From a248e89667c9cd55182e4977ce61f81d95cf9991 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Tue, 22 Oct 2013 12:30:03 +0100 Subject: [PATCH 01/14] No longer using "pimped" list method on array for curry --- src/main/javascript/monet-pimp.js | 4 ++++ src/main/javascript/monet.js | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/javascript/monet-pimp.js b/src/main/javascript/monet-pimp.js index 18b4785..7dbd31a 100644 --- a/src/main/javascript/monet-pimp.js +++ b/src/main/javascript/monet-pimp.js @@ -26,6 +26,10 @@ return Validation.fail(this) } + Array.prototype.list = function () { + return List.fromArray(this) + } + return this })(window || this); \ No newline at end of file diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index ea4324b..d8d7a3c 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -10,7 +10,7 @@ var curry = function (fn, args) { return function () { - var args1 = args.append(Array.prototype.slice.call(arguments).list()); + var args1 = args.append(List.fromArray(Array.prototype.slice.call(arguments))); return args1.size() == fn.length ? fn.apply(this, args1.toArray()) : curry(fn, args1) } } @@ -165,16 +165,17 @@ List.prototype.concat = List.prototype.append - - Array.prototype.list = function () { + List.fromArray = function (array) { var l = Nil - for (i = this.length; i--; i <= 0) { - l = l.cons(this[i]) + for (i = array.length; i--; i <= 0) { + l = l.cons(array[i]) } return l + } + /* Maybe Monad */ var Maybe = window.Maybe = {} From 33ff2cf7b8c5c82bd83d24a87a3d72673d97427c Mon Sep 17 00:00:00 2001 From: bskyb-myersch Date: Tue, 22 Oct 2013 12:31:15 +0100 Subject: [PATCH 02/14] Version bump --- bower.json | 2 +- src/main/javascript/monet-pimp.js | 2 +- src/main/javascript/monet.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index bc3f816..96dffb4 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "monet", - "version": "0.6.3", + "version": "0.6.4", "main": ["src/main/javascript/monet.js","src/main/javascript/monet-pimp.js"], "ignore": [ "**/.*", diff --git a/src/main/javascript/monet-pimp.js b/src/main/javascript/monet-pimp.js index 7dbd31a..1100c29 100644 --- a/src/main/javascript/monet-pimp.js +++ b/src/main/javascript/monet-pimp.js @@ -1,4 +1,4 @@ -// monet-pimp.js 0.6.3 +// monet-pimp.js 0.6.4 // This file needs to be included after monet.js diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index d8d7a3c..43aa8e7 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -1,4 +1,4 @@ -// Monet.js 0.6.3 +// Monet.js 0.6.4 // (c) 2012-2013 Chris Myers // Monet.js may be freely distributed under the MIT license. From 354a302cbb134a2f36e77b0ef123e705c4f44f12 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Sat, 26 Oct 2013 13:32:16 +0100 Subject: [PATCH 03/14] Updating List for FantasyLand --- src/main/javascript/monet.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 43aa8e7..4067ed1 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -109,6 +109,9 @@ this.size_ = tail.size() + 1 } }, + of: function(value) { + return new List(value) + }, size: function () { return this.size_ }, @@ -164,6 +167,9 @@ // Aliases List.prototype.concat = List.prototype.append + List.prototype.bind = List.prototype.chain = List.prototype.flatMap + List.prototype.empty = function(){return Nil} + List.fromArray = function (array) { var l = Nil From 4c7b3edd6dc315414a96ad05d9ccf68614ab25e5 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Wed, 30 Oct 2013 09:20:47 +0000 Subject: [PATCH 04/14] Added `chain` and `of` for Maybe. Added `empty` and `of` for List. --- src/main/javascript/monet.js | 31 ++++--- src/test/javascript/list_spec.js | 10 +++ src/test/javascript/maybe_spec.js | 145 ++++++++++++++++++------------ 3 files changed, 119 insertions(+), 67 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 4067ed1..be29735 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -22,7 +22,7 @@ (function (window) { - var isFunction = function(f) { + var isFunction = function (f) { return !!(f && f.constructor && f.call && f.apply) } @@ -37,9 +37,9 @@ }; - var swap = function(f) { - return function(a,b) { - return f(b,a) + var swap = function (f) { + return function (a, b) { + return f(b, a) } } @@ -88,7 +88,7 @@ }).map(listReverse) } - var listReverse = function(list) { + var listReverse = function (list) { return list.foldLeft(Nil)(swap(cons)) } @@ -109,7 +109,7 @@ this.size_ = tail.size() + 1 } }, - of: function(value) { + of: function (value) { return new List(value) }, size: function () { @@ -146,8 +146,8 @@ return foldRight(append, this, Nil) }, - reverse: function() { - return listReverse(this) + reverse: function () { + return listReverse(this) }, flatMap: function (fn) { return this.map(fn).flatten() @@ -168,7 +168,9 @@ List.prototype.concat = List.prototype.append List.prototype.bind = List.prototype.chain = List.prototype.flatMap - List.prototype.empty = function(){return Nil} + List.prototype.empty = function () { + return Nil + } List.fromArray = function (array) { @@ -180,6 +182,9 @@ } + List.of = function (a) { + return new List(a, Nil) + } /* Maybe Monad */ @@ -190,6 +195,10 @@ return (val == undefined || val == null) ? Maybe.none() : Maybe.some(val) }; + Maybe.of = function (a) { + return Some(a) + } + var Some = Just = Maybe.Just = Maybe.just = Maybe.Some = Maybe.some = window.Some = window.Just = function (val) { return new Some.fn.init(val) }; @@ -244,7 +253,7 @@ // aliases Some.prototype.orJust = Some.prototype.orSome Some.prototype.just = Some.prototype.some - Some.prototype.flatMap = Some.prototype.bind + Some.prototype.flatMap = Some.prototype.chain = Some.prototype.bind Some.fn.init.prototype = Some.fn @@ -280,7 +289,7 @@ }; // aliases - None.prototype.flatMap = None.prototype.bind + None.prototype.flatMap = None.prototype.chain = None.prototype.bind None.fn.init.prototype = None.fn; diff --git a/src/test/javascript/list_spec.js b/src/test/javascript/list_spec.js index 43c5624..dfdf94a 100644 --- a/src/test/javascript/list_spec.js +++ b/src/test/javascript/list_spec.js @@ -121,4 +121,14 @@ describe("An immutable list", function () { }) }) }) + describe("complies with FantasyLand spec for", function() { + it("'of'", function() { + expect(List.of("some val").toArray()).toEqual(["some val"]) + }) + describe("'chain'", function() { + it("being associative", function(){ + + }) + }) + }) }) \ No newline at end of file diff --git a/src/test/javascript/maybe_spec.js b/src/test/javascript/maybe_spec.js index 862820a..bbd8081 100644 --- a/src/test/javascript/maybe_spec.js +++ b/src/test/javascript/maybe_spec.js @@ -1,83 +1,105 @@ -describe('A Maybe', function() { +describe('A Maybe', function () { - beforeEach(function() { - this.addMatchers({ - toBeSomeMaybe: function(expected) { - return this.actual.isSome(); - }, - toBeSomeMaybeWith: function(expected) { - return this.actual.some() == expected - }, - toBeNoneMaybe: function() { - return this.actual.isNone() - } - }); + beforeEach(function () { + this.addMatchers({ + toBeSomeMaybe: function (expected) { + return this.actual.isSome(); + }, + toBeSomeMaybeWith: function (expected) { + return this.actual.some() == expected + }, + toBeNoneMaybe: function () { + return this.actual.isNone() + } + }); }); var someString = Maybe.Some("abcd") var none = Maybe.none() - describe('with a value', function() { - it('will be transformed by a map', function() { - expect(someString.map(function(val){ + describe('with a value', function () { + it('will be transformed by a map', function () { + expect(someString.map(function (val) { return val.length - })).toBeSomeMaybeWith(4) + })).toBeSomeMaybeWith(4) }) - it('will be will true for isSome()', function() { + it('will be will true for isSome()', function () { expect(someString.isSome()).toBeTruthy() }) - it('will be will true for isJust()', function() { + it('will be will true for isJust()', function () { expect(someString.isJust()).toBeTruthy() }) - it('will be false for isNone()', function() { + it('will be false for isNone()', function () { expect(someString.isNone()).toBeFalsy() expect(someString.isNothing()).toBeFalsy() }) - it('will be transformed by a bind', function() { - expect(someString.bind(function(val){return Maybe.some('Hello')})).toBeSomeMaybeWith('Hello') - }) - it('will be transformed by a flatMap', function() { - expect(someString.flatMap(function(val){return Maybe.some('Hello')})).toBeSomeMaybeWith('Hello') - }) - it('will be transformed to a none on bind that returns none', function() { - expect(someString.bind(function(val){return Maybe.none()})).toBeNoneMaybe() - expect(someString.flatMap(function(val){return Maybe.none()})).toBeNoneMaybe() - }) - it ('will return the value when orSome() is called', function() { + it('will be transformed by a bind', function () { + expect(someString.bind(function (val) { + return Maybe.some('Hello') + })).toBeSomeMaybeWith('Hello') + }) + it('will be transformed by a flatMap', function () { + expect(someString.flatMap(function (val) { + return Maybe.some('Hello') + })).toBeSomeMaybeWith('Hello') + }) + it('will be transformed to a none on bind that returns none', function () { + expect(someString.bind(function (val) { + return Maybe.none() + })).toBeNoneMaybe() + expect(someString.flatMap(function (val) { + return Maybe.none() + })).toBeNoneMaybe() + }) + it('will return the value when orSome() is called', function () { expect(someString.orSome('no no!')).toBe('abcd') expect(someString.orJust('no no!')).toBe('abcd') }) }) - describe('without a value', function() { - it('will stay a None type after a map', function() { - expect(none.map(function(val){return val.length}).isNone()).toBeTruthy() + describe('without a value', function () { + it('will stay a None type after a map', function () { + expect(none.map(function (val) { + return val.length + }).isNone()).toBeTruthy() }) - it('will throw an exception when some() is called', function() { + it('will throw an exception when some() is called', function () { expect(none.some).toThrow("Illegal state exception") }) - it('will be true for isNone()', function(){ + it('will be true for isNone()', function () { expect(none.isNone()).toBeTruthy() expect(none.isNothing()).toBeTruthy() }) - it('will be false for isSome()', function(){ + it('will be false for isSome()', function () { expect(none.isSome()).toBeFalsy() }) - it('will always return a none on bind', function() { - expect(none.bind(function() { return Maybe.some('a')})).toBeNoneMaybe() - expect(none.flatMap(function() { return Maybe.some('a')})).toBeNoneMaybe() - expect(none.bind(function() { return Maybe.none()})).toBeNoneMaybe() - expect(none.flatMap(function() { return Maybe.none()})).toBeNoneMaybe() - }) - it ('will return the other value when orSome() is called', function() { + it('will always return a none on bind', function () { + expect(none.bind(function () { + return Maybe.some('a') + })).toBeNoneMaybe() + expect(none.flatMap(function () { + return Maybe.some('a') + })).toBeNoneMaybe() + expect(none.bind(function () { + return Maybe.none() + })).toBeNoneMaybe() + expect(none.flatMap(function () { + return Maybe.none() + })).toBeNoneMaybe() + }) + it('will return the other value when orSome() is called', function () { expect(none.orSome('yep')).toBe('yep') }) }) - describe('Some constructed without a value', function() { - it('will throw an exception', function(){ - expect(function(){Maybe.some()}).toThrow('Illegal state exception') - expect(function(){Maybe.just()}).toThrow('Illegal state exception') + describe('Some constructed without a value', function () { + it('will throw an exception', function () { + expect(function () { + Maybe.some() + }).toThrow('Illegal state exception') + expect(function () { + Maybe.just() + }).toThrow('Illegal state exception') }) }) @@ -89,12 +111,12 @@ describe('A Maybe', function() { var maybeSurname = Maybe.just('Baker') var maybeForename = Maybe.just('Tom') - describe('Applicative functor pattern', function() { - it('will produce a person object if all maybes contain values', function() { + describe('Applicative functor pattern', function () { + it('will produce a person object if all maybes contain values', function () { var personString = maybeAddress.ap(maybeSurname.ap(maybeForename.map(person))).just() expect(personString).toBe("Tom Baker lives at Dulwich, London") }) - it('will not produce a person object if any maybes do not contain values', function() { + it('will not produce a person object if any maybes do not contain values', function () { var result = maybeAddress.ap(Maybe.nothing().ap(maybeForename.map(person))) expect(result).toBeNoneMaybe() }) @@ -109,21 +131,32 @@ describe('A Maybe', function() { expect(Maybe.fromNull(null)).toBeNoneMaybe() }) }) - describe('will create a some for',function() { - it('string', function() { + describe('will create a some for', function () { + it('string', function () { expect(Maybe.fromNull("asdf")).toBeSomeMaybe("asdf") }) }) }) - describe("will pimp an object", function() { - it ("with some", function(){ + describe("will pimp an object", function () { + it("with some", function () { expect("hello".some()).toBeSomeMaybeWith("hello") }) - it("with just", function(){ + it("with just", function () { expect("hello".just()).toBeSomeMaybeWith("hello") }) }) + describe("complies with FantasyLand spec for", function () { + it("'of'", function () { + expect(Maybe.of("hello")).toBeSomeMaybeWith("hello") + }) + it("'chain'", function() { + expect(Maybe.of("hello").chain(function(a){return Maybe.of(a+" world")})).toBeSomeMaybeWith("hello world") + expect(None().chain(function(a){return Maybe.of(a+" world")})).toBeNoneMaybe() + }) + }) + + }) \ No newline at end of file From f5c731fe7ca475da5d7c7fb47860031602ad190d Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Thu, 31 Oct 2013 18:31:19 +0000 Subject: [PATCH 05/14] Added specs for Either --- src/main/javascript/monet-pimp.js | 8 ++ src/main/javascript/monet.js | 80 +++++++++++++++-- src/test/javascript/either_spec.js | 135 +++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 src/test/javascript/either_spec.js diff --git a/src/main/javascript/monet-pimp.js b/src/main/javascript/monet-pimp.js index 1100c29..e3a4e19 100644 --- a/src/main/javascript/monet-pimp.js +++ b/src/main/javascript/monet-pimp.js @@ -26,6 +26,14 @@ return Validation.fail(this) } + Object.prototype.right = function() { + return Either.Right(this) + } + + Object.prototype.left = function() { + return Either.Left(this) + } + Array.prototype.list = function () { return List.fromArray(this) } diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index be29735..5e81e8c 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -223,9 +223,6 @@ map: function (fn) { return new Some(fn(this.val)) - }, - map2: function (maybeB) { - }, isSome: trueFunction, isJust: trueFunction, @@ -306,7 +303,7 @@ } - var Success = Validation.Success = Validation.success = function (val) { + var Success = Validation.Success = Validation.success = window.Success = function (val) { return new Success.fn.init(val) } @@ -352,7 +349,7 @@ Success.fn.init.prototype = Success.fn; - var Fail = Validation.Fail = Validation.fail = function (error) { + var Fail = Validation.Fail = Validation.fail = window.Fail = function (error) { return new Fail.fn.init(error) }; @@ -479,6 +476,79 @@ IO.fn.init.prototype = IO.fn; + /* Either Monad */ + + var Either = window.Either = {} + + + Either.of = function (a) { + return Right(a) + } + + var Right = Either.Right = window.Right = function (val) { + return new Either.fn.init(val, true) + }; + var Left = Either.Left = window.Left = function (val) { + return new Either.fn.init(val, false) + }; + + Either.map2 = function (fn) { + return function (a, b) { + return a.flatMap(function (a1) { + return b.map(function (b1) { + return fn(a1, b1) + }) + }) + } + } + + Either.fn = Either.prototype = { + init: function (val, isRightValue) { + this.isRightValue = isRightValue + this.value = val + }, + map: function (fn) { + return this.isRightValue ? Right(fn(this.value)) : this + }, + flatMap: function (fn) { + return this.isRightValue ? fn(this.value) : this + }, + ap: function (eitherWithFn) { + var self = this + return this.isRightValue ? eitherWithFn.map(function (fn) { + return fn(self.value) + }) : this + }, + isRight: function () { + return this.isRightValue + }, + isLeft: function () { + return !this.isRight() + }, + right: function () { + if (this.isRightValue) { + return this.value + } else { + throw "Illegal state. Cannot call right() on a Either.left" + } + }, + left: function () { + if (this.isRightValue) { + throw "Illegal state. Cannot call left() on a Either.right" + } else { + return this.value + } + }, + cata: function (leftFn, rightFn) { + return this.isRightValue ? rightFn(this.value) : leftFn(this.value) + } + } + + Either.prototype.bind = Either.prototype.chain = Either.prototype.flatMap + + Either.fn.init.prototype = Either.fn; + + Function.prototype.io = function () { return IO(this) } diff --git a/src/test/javascript/either_spec.js b/src/test/javascript/either_spec.js new file mode 100644 index 0000000..4de2565 --- /dev/null +++ b/src/test/javascript/either_spec.js @@ -0,0 +1,135 @@ +/** + * Created by chris on 31/10/2013. + */ + +describe('An Either', function () { + + beforeEach(function () { + this.addMatchers({ + toBeRight: function (expected) { + return this.actual.isRight(); + }, + toBeRightWith: function (expected) { + return this.actual.right() == expected + }, + toBeLeft: function () { + return this.actual.isLeft() + }, + toBeLeftWith: function (expected) { + return this.actual.left() == expected + } + }); + }); + var rightString = Either.Right("abcd") + describe('that is right', function () { + it('will be transformed by a map', function () { + expect(rightString.map(function (val) { + return val.length + })).toBeRightWith(4) + }) + it('will return true when isRight is called', function () { + expect(rightString.isRight()).toBeTruthy() + }) + it('will return value when right is called', function () { + expect(rightString.right()).toBe("abcd") + }) + it('will return false when isLeft is called', function () { + expect(rightString.isLeft()).toBeFalsy() + }) + it('will throw error when left() is called', function () { + expect(function() {rightString.left()}).toThrow('Illegal state. Cannot call left() on a Either.right') + }) + it('will be transformed by a bind', function () { + expect(rightString.bind(function (val) {return Either.Right("efgh")})).toBeRightWith("efgh") + expect(rightString.bind(function (val) {return Either.Left("big left")})).toBeLeftWith("big left") + expect(rightString.flatMap(function (val) {return Either.Right("efgh")})).toBeRightWith("efgh") + expect(rightString.flatMap(function (val) {return Either.Left("big left")})).toBeLeftWith("big left") + }) + it('will run the right side of cata', function () { + expect(rightString.cata(function(val){ + throw "left" + },function(val){ + return "right "+val + })).toBe("right abcd") + }) + }) + + var leftString = Either.Left("error dude") + describe('that is a left', function () { + it('will not be transformed by a map', function () { + expect(leftString.map(function (val) { + return "butterfly" + })).toBeLeftWith("error dude") + }) + it('will not be transformed by a bind', function () { + expect(leftString.bind(function (val) {return Either.Right("efgh")})).toBeLeftWith("error dude") + expect(leftString.bind(function (val) {return Either.Left("big left")})).toBeLeftWith("error dude") + expect(leftString.flatMap(function (val) {return Either.Right("efgh")})).toBeLeftWith("error dude") + expect(leftString.flatMap(function (val) {return Either.Left("big left")})).toBeLeftWith("error dude") + }) + it('will return false when isRight is called', function () { + expect(leftString.isRight()).toBeFalsy() + }) + it('will return error value when left() is called', function () { + expect(leftString.left()).toBe("error dude") + }) + it('will return true when isLeft is called', function () { + expect(leftString.isLeft()).toBeTruthy() + }) + it('will throw error when right() is called', function () { + expect(function(){leftString.right()}).toThrow('Illegal state. Cannot call right() on a Either.left') + }) + it('will run the left side of cata', function () { + expect(leftString.cata(function(val){ + return "left: "+val + }, function(val){ + throw "right" + })).toBe("left: error dude") + }) + + }) + + var person = function (forename, surname, address) { + return forename + " " + surname + " lives at " + address + }.curry(); + + + var validateAddress = Either.Right('Dulwich, London') + var validateSurname = Either.Right('Baker') + var validateForename = Either.Right('Tom') + + describe('Applicative functor pattern', function () { + it('will produce a person object if all validations are rights', function () { + var personString = validateAddress.ap(validateSurname.ap(validateForename.map(person))).right() + expect(personString).toBe("Tom Baker lives at Dulwich, London") + }) + it('will not produce a person object if any validations are lefts', function () { + var result = validateAddress.ap(Either.Left(["no surname"]).ap(validateForename.map(person))) + expect(result).toBeLeftWith("no surname") + }) + it('will stop on first error if any validations are list type lefts', function () { + var result = Either.Left(["no address"]).ap(Either.Left(["no surname"]).ap(validateForename.map(person))) + expect(result.left()[0]).toBe("no address") + }) + it('will accumulate errors if any validations are string type lefts', function () { + var result = Either.Left("no address").ap(Either.Left("no surname").ap(validateForename.map(person))) + expect(result.left()).toBe("no address") + }) + + }) + + describe("will pimp an object", function() { + it ("with right", function(){ + expect("hello".right()).toBeRightWith("hello") + }) + it("with left on string", function(){ + expect("hello".left()).toBeLeftWith("hello") + }) + it("with left on array", function() { + expect(["hello"].left()).toBeLeft() + expect(["hello"].left().left()[0]).toBe("hello") + }) + + }) + +}) \ No newline at end of file From 7872b5590cdbdd3ed47747f150703fc32f662559 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Thu, 31 Oct 2013 21:21:40 +0000 Subject: [PATCH 06/14] Breaking change, dumped lowercase constructors for Maybe, some(), just(), none(), nothing() in favour of the uppercase ones, Some(), Just(), None(), Nothing(). Rewrote Maybe monad into a more compact version. --- src/main/javascript/monet.js | 87 +++++++------------ src/test/javascript/maybe_spec.js | 32 +++---- src/test/javascript/monad_transformer_spec.js | 4 +- 3 files changed, 51 insertions(+), 72 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 5e81e8c..e9e963e 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -192,15 +192,19 @@ var Maybe = window.Maybe = {} Maybe.fromNull = function (val) { - return (val == undefined || val == null) ? Maybe.none() : Maybe.some(val) + return (val == undefined || val == null) ? Maybe.None() : Maybe.Some(val) }; Maybe.of = function (a) { return Some(a) } - var Some = Just = Maybe.Just = Maybe.just = Maybe.Some = Maybe.some = window.Some = window.Just = function (val) { - return new Some.fn.init(val) + var Some = Just = Maybe.Just = Maybe.Some = window.Some = window.Just = function (val) { + return new Maybe.fn.init(true, val) + }; + + var None = Nothing = Maybe.Nothing = Maybe.None = window.None = function () { + return new Maybe.fn.init(false, null) }; Maybe.map2 = function (fn) { @@ -213,82 +217,57 @@ } } - Some.fn = Some.prototype = { - init: function (val) { - if (val == null) { + Maybe.fn = Maybe.prototype = { + init: function (isValue, val) { + this.isValue = isValue + if (val == null && isValue) { throw "Illegal state exception" } this.val = val }, map: function (fn) { - return new Some(fn(this.val)) + return this.bind(Maybe.of.compose(fn)) + }, + isSome: function () { + return this.isValue + }, + isNone: function () { + return !this.isSome() }, - isSome: trueFunction, - isJust: trueFunction, - isNone: falseFunction, - isNothing: falseFunction, bind: function (bindFn) { - return bindFn(this.val) + return this.isValue ? bindFn(this.val) : this }, some: function () { - return this.val + if (this.isValue) { + return this.val + } else { + throw "Illegal state exception" + } }, orSome: function (otherValue) { - return this.val + return this.isValue ? this.val : otherValue }, ap: function (maybeWithFunction) { var value = this.val - return maybeWithFunction.map(function (fn) { + return this.isValue ? maybeWithFunction.map(function (fn) { return fn(value) - }) + }) : this } }; // aliases - Some.prototype.orJust = Some.prototype.orSome - Some.prototype.just = Some.prototype.some - Some.prototype.flatMap = Some.prototype.chain = Some.prototype.bind - + Maybe.prototype.orJust = Maybe.prototype.orSome + Maybe.prototype.just = Maybe.prototype.some + Maybe.prototype.isJust = Maybe.prototype.isSome + Maybe.prototype.isNothing = Maybe.prototype.isNone + Maybe.prototype.flatMap = Maybe.prototype.chain = Maybe.prototype.bind - Some.fn.init.prototype = Some.fn - - - var None = Nothing = Maybe.Nothing = Maybe.None = Maybe.none = Maybe.nothing = window.None = function () { - return new None.fn.init() - }; - var illegalStateFunction = function () { - throw "Illegal state exception" - }; - None.fn = None.prototype = { - init: function (val) { - }, - - map: function () { - return this - }, - isSome: falseFunction, - isNone: trueFunction, - isNothing: trueFunction, - bind: function (bindFn) { - return this - }, - some: illegalStateFunction, - just: illegalStateFunction, - orSome: idFunction, - orJust: idFunction, - ap: function (maybeWithFunction) { - return this; - } - }; - - // aliases - None.prototype.flatMap = None.prototype.chain = None.prototype.bind + Maybe.fn.init.prototype = Maybe.fn - None.fn.init.prototype = None.fn; var Validation = window.Validation = {}; diff --git a/src/test/javascript/maybe_spec.js b/src/test/javascript/maybe_spec.js index bbd8081..5bcdc3c 100644 --- a/src/test/javascript/maybe_spec.js +++ b/src/test/javascript/maybe_spec.js @@ -16,7 +16,7 @@ describe('A Maybe', function () { }); var someString = Maybe.Some("abcd") - var none = Maybe.none() + var none = Maybe.None() describe('with a value', function () { it('will be transformed by a map', function () { expect(someString.map(function (val) { @@ -35,20 +35,20 @@ describe('A Maybe', function () { }) it('will be transformed by a bind', function () { expect(someString.bind(function (val) { - return Maybe.some('Hello') + return Maybe.Some('Hello') })).toBeSomeMaybeWith('Hello') }) it('will be transformed by a flatMap', function () { expect(someString.flatMap(function (val) { - return Maybe.some('Hello') + return Maybe.Some('Hello') })).toBeSomeMaybeWith('Hello') }) it('will be transformed to a none on bind that returns none', function () { expect(someString.bind(function (val) { - return Maybe.none() + return Maybe.None() })).toBeNoneMaybe() expect(someString.flatMap(function (val) { - return Maybe.none() + return Maybe.None() })).toBeNoneMaybe() }) it('will return the value when orSome() is called', function () { @@ -63,7 +63,7 @@ describe('A Maybe', function () { return val.length }).isNone()).toBeTruthy() }) - it('will throw an exception when some() is called', function () { + it('will throw an exception when Some() is called', function () { expect(none.some).toThrow("Illegal state exception") }) it('will be true for isNone()', function () { @@ -75,16 +75,16 @@ describe('A Maybe', function () { }) it('will always return a none on bind', function () { expect(none.bind(function () { - return Maybe.some('a') + return Maybe.Some('a') })).toBeNoneMaybe() expect(none.flatMap(function () { - return Maybe.some('a') + return Maybe.Some('a') })).toBeNoneMaybe() expect(none.bind(function () { - return Maybe.none() + return Maybe.None() })).toBeNoneMaybe() expect(none.flatMap(function () { - return Maybe.none() + return Maybe.None() })).toBeNoneMaybe() }) it('will return the other value when orSome() is called', function () { @@ -95,10 +95,10 @@ describe('A Maybe', function () { describe('Some constructed without a value', function () { it('will throw an exception', function () { expect(function () { - Maybe.some() + Maybe.Some() }).toThrow('Illegal state exception') expect(function () { - Maybe.just() + Maybe.Just() }).toThrow('Illegal state exception') }) }) @@ -107,9 +107,9 @@ describe('A Maybe', function () { return forename + " " + surname + " lives at " + address }.curry() - var maybeAddress = Maybe.just('Dulwich, London') - var maybeSurname = Maybe.just('Baker') - var maybeForename = Maybe.just('Tom') + var maybeAddress = Maybe.Just('Dulwich, London') + var maybeSurname = Maybe.Just('Baker') + var maybeForename = Maybe.Just('Tom') describe('Applicative functor pattern', function () { it('will produce a person object if all maybes contain values', function () { @@ -117,7 +117,7 @@ describe('A Maybe', function () { expect(personString).toBe("Tom Baker lives at Dulwich, London") }) it('will not produce a person object if any maybes do not contain values', function () { - var result = maybeAddress.ap(Maybe.nothing().ap(maybeForename.map(person))) + var result = maybeAddress.ap(Maybe.Nothing().ap(maybeForename.map(person))) expect(result).toBeNoneMaybe() }) }) diff --git a/src/test/javascript/monad_transformer_spec.js b/src/test/javascript/monad_transformer_spec.js index d6865a0..610890c 100644 --- a/src/test/javascript/monad_transformer_spec.js +++ b/src/test/javascript/monad_transformer_spec.js @@ -15,7 +15,7 @@ describe('A Monad Transformer', function () { }); function create(a) { - return monadT(IO(function() {return Maybe.some(a)})) + return monadT(IO(function() {return Maybe.Some(a)})) } var maybeIO = create("hi") @@ -26,7 +26,7 @@ describe('A Monad Transformer', function () { }) it('will allow a containing value to be flatMapped', function() { - var flatmappedMaybeIO = maybeIO.flatMap(function (s) { return Maybe.some("w00t")}) + var flatmappedMaybeIO = maybeIO.flatMap(function (s) { return Maybe.Some("w00t")}) expect(flatmappedMaybeIO.perform().run()).toBeSomeMaybeWith("w00t") }) From 35ed8834fd1d5d476dcd9cbe8051f26346edf5d2 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Tue, 5 Nov 2013 23:00:44 +0000 Subject: [PATCH 07/14] Making map's implementation as a derivation from `bind` and `of` --- src/main/javascript/monet.js | 40 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index e9e963e..2a1c426 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -43,6 +43,10 @@ } } + map = function (fn) { + return this.bind(this.of.compose(fn)) + } + Function.prototype.curry = function () { return curry(this, Nil) } @@ -227,7 +231,7 @@ }, map: function (fn) { - return this.bind(Maybe.of.compose(fn)) + return map.call(this, fn) }, isSome: function () { return this.isValue @@ -254,7 +258,8 @@ return this.isValue ? maybeWithFunction.map(function (fn) { return fn(value) }) : this - } + }, + of: Maybe.of }; @@ -390,6 +395,10 @@ return new MonadT.fn.init(monad) } + MonadT.of = function (m) { + return MonadT(m) + } + MonadT.fn = MonadT.prototype = { init: function (monad) { this.monad = monad @@ -413,16 +422,21 @@ }, perform: function () { return this.monad; - } + }, + of: MonadT.of } MonadT.fn.init.prototype = MonadT.fn; - MonadT.prototype.bind = MonadT.prototype.flatMap; + MonadT.prototype.bind = MonadT.prototype.chain = MonadT.prototype.flatMap; var IO = io = window.IO = window.io = function (effectFn) { return new IO.fn.init(effectFn) } + IO.of = function (fn) { + return IO(fn) + } + IO.fn = IO.prototype = { init: function (effectFn) { this.effectFn = effectFn; @@ -439,22 +453,17 @@ return fn(self.effectFn()).run() }); }, - bind: function (fn) { - return this.flatMap(fn) - }, run: function () { return this.effectFn() }, - perform: function () { - return this.run() - }, - performUnsafeIO: function () { - return this.run() - } + of: IO.of } IO.fn.init.prototype = IO.fn; + IO.prototype.bind = IO.prototype.chain = IO.prototype.flatMap + IO.prototype.perform = IO.prototype.performUnsafeIO = IO.prototype.run + /* Either Monad */ var Either = window.Either = {} @@ -559,5 +568,8 @@ return this -}(window || this)); +} + (window || this) + ) +; From 6280682fc82039b8392019bf9ee768ec141922cb Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Fri, 24 Jan 2014 18:04:11 +1100 Subject: [PATCH 08/14] Happy New Year --- src/main/javascript/monet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 2a1c426..c8d5ec0 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -1,6 +1,6 @@ // Monet.js 0.6.4 -// (c) 2012-2013 Chris Myers +// (c) 2012-2014 Chris Myers // Monet.js may be freely distributed under the MIT license. // For all details and documentation: // http://cwmyers.github.com/monet.js From cf4d57744c6469ce95e4e5484d3d722ab8ed08aa Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Thu, 13 Feb 2014 18:03:45 +1100 Subject: [PATCH 09/14] Darn formatting --- src/main/javascript/monet.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index c8d5ec0..d8fb08e 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -569,7 +569,5 @@ return this } - (window || this) - ) -; +(window || this)); From 628f81bb64739c4a0af4f79ada696341cc345e8a Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Tue, 18 Feb 2014 21:03:20 +1100 Subject: [PATCH 10/14] Added NonEmpty List --- src/main/javascript/monet.js | 96 ++++++++++++++++++++++++++++---- src/test/javascript/list_spec.js | 13 +++-- src/test/javascript/nel_spec.js | 24 ++++++++ 3 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 src/test/javascript/nel_spec.js diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index d8fb08e..4ba53ac 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -43,7 +43,7 @@ } } - map = function (fn) { + var map = function (fn) { return this.bind(this.of.compose(fn)) } @@ -62,20 +62,20 @@ if (l.isNil) { return l } else { - return listMap(fn, l.tail).cons(fn(l.head)) + return listMap(fn, l.tail()).cons(fn(l.head())) } } var foldLeft = function (fn, acc, l) { - return l.isNil ? acc : foldLeft(fn, fn(acc, l.head), l.tail) + return l.isNil ? acc : foldLeft(fn, fn(acc, l.head()), l.tail()) } var foldRight = function (fn, l, acc) { - return l.isNil ? acc : fn(l.head, foldRight(fn, l.tail, acc)) + return l.isNil ? acc : fn(l.head(), foldRight(fn, l.tail(), acc)) } var append = function (list1, list2) { - return list1.isNil ? list2 : append(list1.tail, list2).cons(list1.head) + return list1.isNil ? list2 : append(list1.tail(), list2).cons(list1.head()) } var sequenceMaybe = function (list) { @@ -108,9 +108,9 @@ this.size_ = 0 } else { this.isNil = false - this.head = head - this.tail = (tail == undefined || tail == null) ? Nil : tail - this.size_ = tail.size() + 1 + this.head_ = head + this.tail_ = (tail == undefined || tail == null) ? Nil : tail + this.size_ = (tail == undefined || tail == null) ? 0 : tail.size() + 1 } }, of: function (value) { @@ -122,6 +122,9 @@ cons: function (head) { return List(head, this) }, + snoc: function (element) { + return this.concat(List(element)) + }, map: function (fn) { return listMap(fn, this) }, @@ -154,7 +157,8 @@ return listReverse(this) }, flatMap: function (fn) { - return this.map(fn).flatten() + var mappedlist = this.map(fn); + return mappedlist.flatten() }, // transforms a list of Maybes to a Maybe list sequenceMaybe: function () { @@ -162,7 +166,14 @@ }, sequenceValidation: function () { return sequenceValidation(this) - } + }, + head: function () { + return this.head_ + }, + tail: function () { + return this.isNil ? Nil : this.tail_ + }, + isNEL: falseFunction } List.fn.init.prototype = List.fn; @@ -190,6 +201,68 @@ return new List(a, Nil) } + /* + * Non-Empty List monad + * This is also a comonad because there exists the implementation of extract, which is just head + * + */ + + + var NEL = window.NEL = function (head, tail) { + if (head == undefined || head == null) { + throw "Cannot create an empty Non-Empty List." + } + return new NEL.fn.init(head, tail) + } + + NEL.fn = NEL.prototype = { + init: function (head, tail) { + if (head == undefined || head == null) { + this.isNil = true + this.size_ = 0 + } else { + this.isNil = false + this.head_ = head + this.tail_ = (tail == undefined || tail == null) ? Nil : tail + this.size_ = this.tail_.size() + } + }, + map: function (fn) { + return NEL(fn(this.head_), listMap(fn, this.tail_)) + }, + + bind: function (fn) { + var p = fn(this.head_) + if (!p.isNEL()) { + throw "function must return a NonEmptyList." + } + var list = this.tail().foldLeft(Nil.snoc(p.head()).append(p.tail()))(function (acc, e) { + var list2 = fn(e).toList() + return acc.snoc(list2.head()).append(list2.tail()) + }) + + return new NEL(list.head(), list.tail()) + + }, + + head: function () { + return this.head_ + }, + + tail: function () { + return this.tail_ + }, + toList: function () { + return List(this.head_, this.tail_) + }, + isNEL: trueFunction + } + + NEL.fn.init.prototype = NEL.fn; + NEL.prototype.toArray = List.prototype.toArray + NEL.prototype.flatMap = NEL.prototype.bind + NEL.prototype.extract = NEL.prototype.head + /* Maybe Monad */ @@ -568,6 +641,5 @@ return this -} -(window || this)); +}(window || this)); diff --git a/src/test/javascript/list_spec.js b/src/test/javascript/list_spec.js index dfdf94a..7d7fd84 100644 --- a/src/test/javascript/list_spec.js +++ b/src/test/javascript/list_spec.js @@ -34,10 +34,10 @@ describe("An immutable list", function () { it("can be mapped", function () { var mappedList = list.map(plusOne) - expect(mappedList.head).toBe(2) - expect(mappedList.tail.head).toBe(3) - expect(mappedList.tail.tail.head).toBe(4) - expect(mappedList.tail.tail.tail.head).toBe(5) + expect(mappedList.head()).toBe(2) + expect(mappedList.tail().head()).toBe(3) + expect(mappedList.tail().tail().head()).toBe(4) + expect(mappedList.tail().tail().tail().head()).toBe(5) }) it("can be reduced using foldLeft", function () { @@ -121,6 +121,11 @@ describe("An immutable list", function () { }) }) }) + describe("that is empty", function() { + it("will return Nil on tail()", function() { + expect(Nil.tail()).toBe(Nil) + }) + }) describe("complies with FantasyLand spec for", function() { it("'of'", function() { expect(List.of("some val").toArray()).toEqual(["some val"]) diff --git a/src/test/javascript/nel_spec.js b/src/test/javascript/nel_spec.js new file mode 100644 index 0000000..9c0a081 --- /dev/null +++ b/src/test/javascript/nel_spec.js @@ -0,0 +1,24 @@ +describe("A Non-Empty immutable list", function () { + + var nonEmptyList = NEL(1, List(2, List(3, List(4, Nil)))) + + it ("cannot be create with zero elements", function() { + expect(function() {new NEL()}).toThrow("Cannot create an empty Non-Empty List.") + }) + + it("must have a head", function() { + expect(new NEL(1,Nil).head()).toEqual(1) + }) + + it("Must be mappable", function() { + expect(new NEL(1, Nil).map(function(a){return a+1}).toArray()).toEqual([2]) + }) + + it("will be transformed by a flatMap", function () { + expect(nonEmptyList.flatMap(function (e) { + return NEL(e*e, List(e+e)) + }).toArray()).toEqual([1, 2, 4, 4, 9, 6, 16, 8]) + }) + +}) + From b4480e67e7fb6961c14d8b1e9994ef60df64b7e2 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Sun, 23 Feb 2014 13:00:20 +1100 Subject: [PATCH 11/14] Added NEL comonad pattern --- src/main/javascript/monet.js | 86 ++++++++++++++++++++++---------- src/test/javascript/list_spec.js | 77 ++++++++++++++++------------ src/test/javascript/nel_spec.js | 53 +++++++++++++++++--- 3 files changed, 153 insertions(+), 63 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 4ba53ac..f080f36 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -59,10 +59,13 @@ } var listMap = function (fn, l) { - if (l.isNil) { - return l - } else { - return listMap(fn, l.tail()).cons(fn(l.head())) + return l.isNil ? l : listMap(fn, l.tail()).cons(fn(l.head())) + } + + var listEach = function (effectFn, l) { + if (!l.isNil) { + effectFn(l.head()) + listEach(effectFn, l.tail()) } } @@ -151,14 +154,18 @@ }, flatten: function () { return foldRight(append, this, Nil) - + }, + flattenMaybe: function() { + return this.flatMap(Maybe.toList) }, reverse: function () { return listReverse(this) }, - flatMap: function (fn) { - var mappedlist = this.map(fn); - return mappedlist.flatten() + bind: function (fn) { + return this.map(fn).flatten() + }, + each: function (effectFn) { + listEach(effectFn, this) }, // transforms a list of Maybes to a Maybe list sequenceMaybe: function () { @@ -173,6 +180,9 @@ tail: function () { return this.isNil ? Nil : this.tail_ }, + tails: function() { + return this.isNil ? List(Nil, Nil) : this.tail().tails().cons(this) + }, isNEL: falseFunction } @@ -182,7 +192,6 @@ // Aliases List.prototype.concat = List.prototype.append - List.prototype.bind = List.prototype.chain = List.prototype.flatMap List.prototype.empty = function () { return Nil } @@ -252,16 +261,31 @@ tail: function () { return this.tail_ }, + tails: function() { + var listsOfNels = this.toList().tails().map(NEL.fromList).flattenMaybe(); + return NEL(listsOfNels.head(), listsOfNels.tail()) + }, toList: function () { return List(this.head_, this.tail_) }, + reverse: function () { + if (this.tail().isNil) { + return this + } else { + var reversedTail = this.tail().reverse() + return NEL(reversedTail.head(), reversedTail.tail().append(List(this.head()))) + } + }, isNEL: trueFunction } + NEL.fromList = function (list) { + return list.isNil ? None() : Some(NEL(list.head(), list.tail())) + } + NEL.fn.init.prototype = NEL.fn; NEL.prototype.toArray = List.prototype.toArray - NEL.prototype.flatMap = NEL.prototype.bind - NEL.prototype.extract = NEL.prototype.head + NEL.prototype.extract = NEL.prototype.copure = NEL.prototype.head /* Maybe Monad */ @@ -294,6 +318,10 @@ } } + Maybe.toList = function (maybe) { + return maybe.toList() + } + Maybe.fn = Maybe.prototype = { init: function (isValue, val) { this.isValue = isValue @@ -332,6 +360,10 @@ return fn(value) }) : this }, + + toList: function() { + return this.map(List).orSome(Nil) + }, of: Maybe.of }; @@ -341,7 +373,6 @@ Maybe.prototype.just = Maybe.prototype.some Maybe.prototype.isJust = Maybe.prototype.isSome Maybe.prototype.isNothing = Maybe.prototype.isNone - Maybe.prototype.flatMap = Maybe.prototype.chain = Maybe.prototype.bind Maybe.fn.init.prototype = Maybe.fn @@ -382,9 +413,6 @@ bind: function (fn) { return fn(this.val); }, - flatMap: function (fn) { - return this.bind(fn) - }, ap: function (validationWithFn) { var value = this.val return validationWithFn.map(function (fn) { @@ -420,9 +448,6 @@ bind: function (fn) { return this; }, - flatMap: function (fn) { - return this.bind(fn) - }, isFail: trueFunction, isSuccess: falseFunction, fail: function () { @@ -481,7 +506,7 @@ return v.map(fn) })) }, - flatMap: function (fn) { + bind: function (fn) { return monadT(this.monad.map(function (v) { return v.flatMap(fn) })) @@ -500,7 +525,6 @@ } MonadT.fn.init.prototype = MonadT.fn; - MonadT.prototype.bind = MonadT.prototype.chain = MonadT.prototype.flatMap; var IO = io = window.IO = window.io = function (effectFn) { return new IO.fn.init(effectFn) @@ -520,7 +544,7 @@ return fn(self.effectFn()) }) }, - flatMap: function (fn) { + bind: function (fn) { var self = this return IO(function () { return fn(self.effectFn()).run() @@ -534,7 +558,6 @@ IO.fn.init.prototype = IO.fn; - IO.prototype.bind = IO.prototype.chain = IO.prototype.flatMap IO.prototype.perform = IO.prototype.performUnsafeIO = IO.prototype.run /* Either Monad */ @@ -571,7 +594,7 @@ map: function (fn) { return this.isRightValue ? Right(fn(this.value)) : this }, - flatMap: function (fn) { + bind: function (fn) { return this.isRightValue ? fn(this.value) : this }, ap: function (eitherWithFn) { @@ -605,8 +628,6 @@ } } - Either.prototype.bind = Either.prototype.chain = Either.prototype.flatMap - Either.fn.init.prototype = Either.fn; @@ -639,6 +660,21 @@ } } + // Wire up aliases + function alias(type) { + type.prototype.flatMap = type.prototype.chain = type.prototype.bind + type.prototype.pure = type.prototype.unit = type.prototype.of + type.pure = type.unit = type.of + } + + alias(MonadT) + alias(Either) + alias(Maybe) + alias(IO) + alias(NEL) + alias(List) + alias(Success) + alias(Fail) return this }(window || this)); diff --git a/src/test/javascript/list_spec.js b/src/test/javascript/list_spec.js index 7d7fd84..3b31b3c 100644 --- a/src/test/javascript/list_spec.js +++ b/src/test/javascript/list_spec.js @@ -23,6 +23,18 @@ describe("An immutable list", function () { return a + 1 }; + it("will return all the possible tails on tails()", function () { + expect(list.tails().map(function (m) { + return m.toArray() + }).toArray()).toEqual([ + [ 1, 2, 3, 4 ], + [ 2, 3, 4 ], + [ 3, 4 ], + [ 4 ], + [ ] + ]) + }) + it("can be converted to Array", function () { expect(list.toArray()).toEqual([1, 2, 3, 4]) @@ -60,25 +72,25 @@ describe("An immutable list", function () { expect(list.append([5, 6, 7].list()).toArray()).toEqual([1, 2, 3, 4, 5, 6, 7]) }) - describe("will flatten inner lists", function() { + describe("will flatten inner lists", function () { it("with two elements", function () { expect([[1, 2].list(), [3, 4].list()].list().flatten().toArray()).toEqual([1, 2, 3, 4]) }) - it("with one element", function() { - expect([[1,2].list()].list().flatten().toArray()).toEqual([1,2]) + it("with one element", function () { + expect([[1, 2].list()].list().flatten().toArray()).toEqual([1, 2]) }) }) - describe("will reverse a list", function() { - it("with 4 elements", function() { - expect(list.reverse().toArray()).toEqual([4,3,2,1]) + describe("will reverse a list", function () { + it("with 4 elements", function () { + expect(list.reverse().toArray()).toEqual([4, 3, 2, 1]) }) - it("with no elements", function() { + it("with no elements", function () { expect(Nil.reverse().toArray()).toEqual([]) }) - it("with one element", function() { - expect(List(1,Nil).reverse().toArray()).toEqual([1]) + it("with one element", function () { + expect(List(1, Nil).reverse().toArray()).toEqual([1]) }) }) @@ -87,51 +99,54 @@ describe("An immutable list", function () { it("with one defined element", function () { expect(List(Some("hello"), Nil).sequenceMaybe().some().toArray()).toEqual(["hello"]) }) - it("with multiple defined elements", function() { - expect([Some(1),Some(2),Some(3)].list().sequenceMaybe().some().toArray()).toEqual([1,2,3]) + it("with multiple defined elements", function () { + expect([Some(1), Some(2), Some(3)].list().sequenceMaybe().some().toArray()).toEqual([1, 2, 3]) }) - it("with multiple defined elements (pimped)", function() { - expect(["1".some(),"2".some(),"3".some()].list().sequenceMaybe().some().toArray()).toEqual(["1","2","3"]) + it("with multiple defined elements (pimped)", function () { + expect(["1".some(), "2".some(), "3".some()].list().sequenceMaybe().some().toArray()).toEqual(["1", "2", "3"]) }) - it("with multiple defined elements and one undefined element", function() { - expect([Some(1),Some(2),None()].list().sequenceMaybe()).toBeNoneMaybe() + it("with multiple defined elements and one undefined element", function () { + expect([Some(1), Some(2), None()].list().sequenceMaybe()).toBeNoneMaybe() }) - it("with no elements", function() { + it("with no elements", function () { expect([].list().sequenceMaybe().some().toArray()).toEqual([]) }) }) - describe("of Validations", function() { - it("with one success element", function() { - expect(List("hello".success(),Nil).sequenceValidation().success().toArray()).toEqual(["hello"]) + describe("of Validations", function () { + it("with one success element", function () { + expect(List("hello".success(), Nil).sequenceValidation().success().toArray()).toEqual(["hello"]) }) - it("with two success elements", function() { - expect(["1".success(),"2".success()].list().sequenceValidation().success().toArray()).toEqual(["1","2"]) + it("with two success elements", function () { + expect(["1".success(), "2".success()].list().sequenceValidation().success().toArray()).toEqual(["1", "2"]) }) - it("with one success element and one fail (in array) element", function() { + it("with one success element and one fail (in array) element", function () { expect(["happy".success(), ["sad"].fail()].list().sequenceValidation().fail()).toEqual(["sad"]) }) - it("with one success element and two failed (in array) element", function() { + it("with one success element and two failed (in array) element", function () { expect(["happy".success(), ["sad"].fail(), ["really sad"].fail()].list().sequenceValidation().fail()).toEqual(["sad", "really sad"]) }) - it("with one success element and one fail (in list) element", function() { + it("with one success element and one fail (in list) element", function () { expect(["happy".success(), ["sad"].list().fail()].list().sequenceValidation().fail().toArray()).toEqual(["sad"]) }) - it("with one success element and two failed (in list) element", function() { + it("with one success element and two failed (in list) element", function () { expect(["happy".success(), ["sad"].list().fail(), ["really sad"].list().fail()].list().sequenceValidation().fail().toArray()).toEqual(["sad", "really sad"]) }) }) }) - describe("that is empty", function() { - it("will return Nil on tail()", function() { + describe("that is empty", function () { + it("will return Nil on tail()", function () { expect(Nil.tail()).toBe(Nil) }) + it("will return a list with an empty list on tails()", function () { + expect(Nil.tails().toArray()).toEqual([Nil]) + }) }) - describe("complies with FantasyLand spec for", function() { - it("'of'", function() { + describe("complies with FantasyLand spec for", function () { + it("'of'", function () { expect(List.of("some val").toArray()).toEqual(["some val"]) }) - describe("'chain'", function() { - it("being associative", function(){ + describe("'chain'", function () { + it("being associative", function () { }) }) diff --git a/src/test/javascript/nel_spec.js b/src/test/javascript/nel_spec.js index 9c0a081..a329c8e 100644 --- a/src/test/javascript/nel_spec.js +++ b/src/test/javascript/nel_spec.js @@ -1,24 +1,63 @@ describe("A Non-Empty immutable list", function () { + beforeEach(function () { + this.addMatchers({ + toBeSomeMaybe: function (expected) { + return this.actual.isSome(); + }, + toBeSomeMaybeWith: function (expected) { + return this.actual.some() == expected + }, + toBeNoneMaybe: function () { + return this.actual.isNone() + } + }); + }) + var nonEmptyList = NEL(1, List(2, List(3, List(4, Nil)))) - it ("cannot be create with zero elements", function() { - expect(function() {new NEL()}).toThrow("Cannot create an empty Non-Empty List.") + it("cannot be create with zero elements", function () { + expect(function () { + new NEL() + }).toThrow("Cannot create an empty Non-Empty List.") }) - it("must have a head", function() { - expect(new NEL(1,Nil).head()).toEqual(1) + it("must have a head", function () { + expect(new NEL(1, Nil).head()).toEqual(1) }) - it("Must be mappable", function() { - expect(new NEL(1, Nil).map(function(a){return a+1}).toArray()).toEqual([2]) + it("Must be mappable", function () { + expect(new NEL(1, Nil).map(function (a) { + return a + 1 + }).toArray()).toEqual([2]) }) it("will be transformed by a flatMap", function () { expect(nonEmptyList.flatMap(function (e) { - return NEL(e*e, List(e+e)) + return NEL(e * e, List(e + e)) }).toArray()).toEqual([1, 2, 4, 4, 9, 6, 16, 8]) }) + it("can be reversed", function () { + expect(nonEmptyList.reverse().toArray()).toEqual([4, 3, 2, 1]) + expect(NEL(1, Nil).reverse().toArray()).toEqual([1]) + }) + + it("can be created from a list", function () { + expect(NEL.fromList([1, 2, 3, 4].list()).some().toArray()).toEqual([1, 2, 3, 4]) + expect(NEL.fromList(List())).toBeNoneMaybe() + }) + + it("will return a NEL of NELs for tails()", function () { + expect(nonEmptyList.tails().map(function (m) { + return m.toArray() + }).toArray()).toEqual([ + [ 1, 2, 3, 4 ], + [ 2, 3, 4 ], + [ 3, 4 ], + [ 4 ] + ]) + }) + }) From dd172020d3ad330f579fbc3279e2141ee0e2ce4d Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Sun, 23 Feb 2014 14:55:50 +1100 Subject: [PATCH 12/14] Implemented cobind, in terms of cojoin and map. --- src/main/javascript/monet.js | 18 ++++++++++++++---- src/test/javascript/nel_spec.js | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index f080f36..02b9179 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -155,7 +155,7 @@ flatten: function () { return foldRight(append, this, Nil) }, - flattenMaybe: function() { + flattenMaybe: function () { return this.flatMap(Maybe.toList) }, reverse: function () { @@ -180,7 +180,7 @@ tail: function () { return this.isNil ? Nil : this.tail_ }, - tails: function() { + tails: function () { return this.isNil ? List(Nil, Nil) : this.tail().tails().cons(this) }, isNEL: falseFunction @@ -261,7 +261,8 @@ tail: function () { return this.tail_ }, - tails: function() { + //NEL[A] -> NEL[NEL[A]] + tails: function () { var listsOfNels = this.toList().tails().map(NEL.fromList).flattenMaybe(); return NEL(listsOfNels.head(), listsOfNels.tail()) }, @@ -276,6 +277,13 @@ return NEL(reversedTail.head(), reversedTail.tail().append(List(this.head()))) } }, + foldLeft: function (initialValue) { + return this.toList().foldLeft(initialValue) + }, + // NEL[A] -> (NEL[A] -> B) -> NEL[B] + cobind: function (fn) { + return this.cojoin().map(fn) + }, isNEL: trueFunction } @@ -286,6 +294,8 @@ NEL.fn.init.prototype = NEL.fn; NEL.prototype.toArray = List.prototype.toArray NEL.prototype.extract = NEL.prototype.copure = NEL.prototype.head + NEL.prototype.cojoin = NEL.prototype.tails + NEL.prototype.coflatMap = NEL.prototype.mapTails = NEL.prototype.cobind /* Maybe Monad */ @@ -361,7 +371,7 @@ }) : this }, - toList: function() { + toList: function () { return this.map(List).orSome(Nil) }, of: Maybe.of diff --git a/src/test/javascript/nel_spec.js b/src/test/javascript/nel_spec.js index a329c8e..a43a176 100644 --- a/src/test/javascript/nel_spec.js +++ b/src/test/javascript/nel_spec.js @@ -59,5 +59,13 @@ describe("A Non-Empty immutable list", function () { ]) }) + it("will map a function over the tails with cobind", function () { + expect(nonEmptyList.cobind(function (nel) { + return nel.foldLeft(0)(function(a,b){ + return a+b + }) + }).toArray()).toEqual([10,9,7,4]) + }) + }) From f789d668f8c7f543397dcc4da29fea4633b7ef26 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Sun, 23 Feb 2014 19:58:54 +1100 Subject: [PATCH 13/14] Formatting and comments --- src/main/javascript/monet.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 02b9179..d185b63 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -212,12 +212,12 @@ /* * Non-Empty List monad - * This is also a comonad because there exists the implementation of extract, which is just head + * This is also a comonad because there exists the implementation of extract(copure), which is just head + * and cobind and cojoin. * */ - - var NEL = window.NEL = function (head, tail) { + var NEL = window.NEL = NonEmptyList = window.NonEmptyList = function (head, tail) { if (head == undefined || head == null) { throw "Cannot create an empty Non-Empty List." } @@ -251,7 +251,6 @@ }) return new NEL(list.head(), list.tail()) - }, head: function () { From d06ee055b36f6b703efff025e48d932b96f070ce Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Sun, 23 Feb 2014 20:21:27 +1100 Subject: [PATCH 14/14] Version bump and updated doco for release 0.6.5 --- README.md | 348 ++++++++++++++++++++++++++---- bower.json | 2 +- src/main/javascript/monet-pimp.js | 2 +- src/main/javascript/monet.js | 2 +- 4 files changed, 308 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 0a80128..90a46f7 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,7 @@ For people who wish they didn't have to programme in JavaScript. Full documentat ## Introduction -So you've been forced to use JavaScript, eh? Well, don't write your will just yet, this library is what you want to -model all those monadic types that JavaScript (and well most languages) thoughtlessly omit. - -If you know what a monad is then you are already an awesome programmer and if you don't, well... awesome is what you are -about to become. +Monet is a library designed to bring great power to your JavaScript programming. It is a tool bag that assists Functional Programming by providing a rich set of Monads and other useful functions. This library is inspired by those that have come before, especially the [FunctionalJava][functionalJava] and [Scalaz][scalaz] projects. @@ -44,6 +40,55 @@ or to install a specific version bower install monet#{{ page.version }} +## A note on types + +#### Well it's JavaScript - there ain't any +As you know JavaScript isn't a strongly typed language. This kinda sucks. Types are a great help when it comes to functional programming as it makes the +code more comprehensible and prevents a range of errors from being introduced. + +Knowing the types of your functions and data is also important when writing documentation (such as this one), so we will invent some type annotations to make things +more clear. We will only do this in the function definition and *not* in the **concrete examples**. + +#### Generic Types + +JavaScript doesn't have generic types but it's useful to know about them when dealing with Monads. For instance the `List` monad is a type that requires another type, such +as a string or integer or some other type before it can be constructed. So you would have a List of Strings or a List of Integers or generically a List of `A`s where `A` is a type you will supply. Now of course this is JavaScript and you can do as you please even though it doesn't make sense. But to make things clearer (hopefully) we will attempt to do show generics or *type parameters* thusly: + + List[A] + +Which means a `List` of `A`s. Though of course you will have to keep track of the types yourself. + +#### Functions + + function x(a: A, b: B): C + +And functions on a Monadic type that has been constructed with `A` + + Maybe[A].fromNull(a: A): Maybe[A] + +##### Anonymous functions + +For functions that take other functions as parameters (which are called *Higher order functions*) we will use an abbreviated way to represent that function +using a pseudo type lambda: + + A -> B + +So, + + function x(a: A -> B, c: B -> C): C + +means that function `x` takes two parameters that are both functions themselves. `a` is a function that takes a type `A` and returns a type `B` and `c` is a function that takes a type `B` and returns a type `C`. The function `x` will return a type `C`. + +##### The Unit type + +Some functions (or lambdas) do not take a parameter, and some do not return anything. Will express this as: + + () -> A + +or + + A -> () + ## Maybe The `Maybe` type is the most common way of representing *nothingness* (or the `null` type) with making the possibilities of `NullPointer` issues disappear. @@ -62,10 +107,11 @@ or more simply with the pimped method on Object. var maybe = val.some() ### Functions -#### map(fn) -`map` takes a function (a -> b) and applies that function to the value inside the `Maybe` and returns another `Maybe`. +#### map - maybe.map(fn) : Maybe + Maybe[A].map(fn: A -> B) : Maybe[B] + +`map` takes a function (A -> B) and applies that function to the value inside the `Maybe` and returns another `Maybe`. For example: @@ -74,10 +120,13 @@ For example: }) => 124 -#### bind(fn) *alias: flatMap* + +#### bind *alias: flatMap* + + Maybe[A].bind(fn: A -> Maybe[B]): Maybe[B] + `bind` takes a function that takes a value and returns an `Maybe`. The value to the function will be supplied from the `Maybe` you are binding on. - maybe.bind(fn) : Maybe For example: @@ -90,23 +139,44 @@ For example: }) -#### isSome() *alias: isJust* +#### isSome *alias: isJust* + + Maybe[A].isSome(): Boolean + `isSome` on a `Some` value will return `true` and will return `false` on a `None`. - maybe.some("hi").isSome() - => true +For example: + + Maybe.some("hi").isSome() + //result: true + +#### isNone *alias: isNothing* + + Maybe[A].isNone(): Boolean -#### isNone() *alias: isNothing* `isNone` on a `None` value will return `true` and will return `false` on a `Some`. +For example: + + Maybe.none().isNone() + //result: true + ####some() *alias: just* -`some` will 'reduce' the `Maybe` to its value. + + Maybe[A].some(): A + +`some` will 'reduce' the `Maybe` to its value. But warning! It will throw an error if you attempt to do this on a none. Use `orSome` instead. + +For example: Maybe.some("hi").some() - => "hi" + //result: "hi" + +####orSome *alias: orJust* + + Maybe[A].orSome(a:A) : A -####orSome(value) *alias: orJust* Will return the containing value inside the `Maybe` or return the supplied value. maybe.some("hi").orSome("bye") @@ -114,10 +184,11 @@ Will return the containing value inside the `Maybe` or return the supplied value Maybe.none().orSome("bye") => "bye" -####ap(Maybe(fn)) -The `ap` function implements the Applicative Functor pattern. It takes as a parameter another `Maybe` type which contains a function, and then applies that function to the value contained in the calling `Maybe`. +####ap + + Maybe[A].ap(Maybe[A->B]): Maybe[B] - maybe.ap(maybeWithfn): Maybe +The `ap` function implements the Applicative Functor pattern. It takes as a parameter another `Maybe` type which contains a function, and then applies that function to the value contained in the calling `Maybe`. It may seem odd to want to apply a function to a monad that exists inside another monad, but this is particular useful for when you have a curried function being applied across many monads. @@ -131,7 +202,9 @@ Here is an example for creating a string out of the result of a couple of `Maybe var maybeSurname = Maybe.just('Baker') var maybeForename = Maybe.just('Tom') - var personString = maybeAddress.ap(maybeSurname.ap(maybeForename.map(person))).just() + var personString = maybeAddress + .ap(maybeSurname + .ap(maybeForename.map(person))).just() // result: "Tom Baker lives in Dulwich, London" @@ -139,7 +212,7 @@ For further reading see [this excellent article](http://learnyouahaskell.com/fun ## Validation -Validation is not quite a monad as it [doesn't quite follow the monad rules](http://stackoverflow.com/questions/12211776/why-isnt-validation-a-monad-scalaz7), even though it has the monad methods. It that can hold either a success value or a failure value (i.e. an error message or some other failure object) and has methods for accumulating errors. +Validation is not quite a monad as it [doesn't quite follow the monad rules](http://stackoverflow.com/questions/12211776/why-isnt-validation-a-monad-scalaz7), even though it has the monad methods. It that can hold either a success value or a failure value (i.e. an error message or some other failure object) and has methods for accumulating errors. We will represent a Validation like this: `Validation[E,A]` where `E` represents the error type and `A` represents the success type. #### Creating a Validation @@ -152,13 +225,22 @@ or with pimped methods on an object var failure = "some error".fail(); ###Functions -####map() -`map` takes a function (a -> b) and applies that function to the value inside the `success` side of the `Validation` and returns another `Validation`. +####map -####bind(fn) *alias: flatMap* -`bind` takes a function that takes a value and returns an `Validation`. The value to the function will be supplied from the `Validation` you are binding on. + Validation[E,A].map(fn:A -> B): Validation[E,A] + +`map` takes a function (A -> B) and applies that function to the value inside the `success` side of the `Validation` and returns another `Validation`. + +For example: - validation.bind(fn) : validation + Validation.success(123).map(function(val) { return val + 1}) + //result: 124 + +####bind *alias: flatMap* + + Validation[E,A].bind(fn:A -> Validation[E,B]) : Validation[E,B] + +`bind` takes a function that takes a value and returns an `Validation`. The value to the function will be supplied from the `Validation` you are binding on. For example: @@ -171,19 +253,34 @@ For example: }) -####isSuccess() +####isSuccess + + Validation[E,A].isSuccess() : Boolean + Will return `true` if this is a successful validation, `false` otherwise. -####isFail() +####isFail + + Validation[E,A].isFail() : Boolean + Will return `false` if this is a failed validation, `true` otherwise. -####success() +####success + + Validation[E,A].success() : A + Will return the successful value. -####fail() + +####fail + + Validation[E,A].fail() : E + Will return the failed value, usually an error message. -####ap(Validation(fn)) +####ap + + Validation[E,A].ap(v: Validation[E, A->B]) : Validation[E,B] Implements the applicative functor pattern. `ap` will apply a function over the validation from within the supplied validation. If any of the validations are `fail`s then the function will collect the errors. @@ -206,10 +303,14 @@ Implements the applicative functor pattern. `ap` will apply a function over the .ap(validateForename.map(person))) // result: Validation(["no address", "no surname"]) -####cata(failFn,successFn) +####cata + + Validation[E,A].cata(failureFn: E->X, successFn: A->X): X The catamorphism for validation. If the validation is `success` the success function will be executed with the success value and the value of the function returned. Otherwise the `failure` function will be called with the failure value. +For example: + var result = v.cata(function(failure) { return "oh dear it failed because " + failure }, function(success) { @@ -224,14 +325,30 @@ The `IO` monad is for isolating effects to maintain referential transparency in var ioAction = IO(function () { return $("#id").val() }) ###Functions -####IO(fn) *alias: io* +####IO *alias: io* + + IO[A](fn: () -> A): IO[A] + The constructor for the `IO` monad. It is a purely functional wrapper around the supplied effect and enables referential transparency in your software. -####bind(fn) *alias: flatMap* + +####bind *alias: flatMap* + + IO[A](fn: A -> IO[B]): IO[B] + Perform a monadic bind (flatMap) over the effect. It takes a function that returns an `IO`. This will happen lazily and will not evaluate the effect. -####map(fn) + +Examples: see below + +####map + + IO[A](fn: A -> B): IO[B] + Performs a map over the result of the effect. This will happen lazily and will not evaluate the effect. + ####run *alias: perform* Evaluates the effect inside the `IO` monad. This can only be run once in your programme and at the very end. + + ###"Pimped" functions ####fn.io() Wraps a supplied function in an `IO`. Assumes no arguments will be supplied to the function. @@ -293,7 +410,7 @@ An immutable list is a list that has a head element and a tail. A tail is anothe #### Creating a list -The easiest way to create a list is with the pimped method on Array. +The easiest way to create a list is with the pimped method on Array, available in monet-pimp.js. var myList = [1,2,3].list() @@ -304,10 +421,14 @@ which is equivalent to: As you can see from the second example each List object contains a head element and the tail is just another list element. ###Functions -####cons(element) +####cons + + List[A].cons(a: A) : List[A] `cons` will prepend the element to the front of the list and return a new list. The existing list remains unchanged. +For example: + var newList = myList.cons(4) // newList.toArray() == [4,1,2,3] // myList.toArray() == [1,2,3] @@ -318,7 +439,9 @@ As you can see from the second example each List object contains a head element var newList = "z".cons(myList) newList.toArray() == ["z","a","b","c"] -####map(fn) +####map + + List[A].map(fn: A->B): List[B] Maps the supplied function over the list. @@ -327,14 +450,19 @@ Maps the supplied function over the list. }) // list == [2,3,4] -####flatMap(fn) *alias: bind()* +####flatMap *alias: bind* + + List[A].flatMap(fn: A -> List[B]): List[B] Maps the supplied function over the list and then flattens the returned list. The supplied function must return a new list. -####foldLeft(initialValue)(function(acc, e)) +####foldLeft + + List[A].foldLeft(initialValue: B)(fn: (acc:B, element:A) -> B): B `foldLeft` takes an initial value and a function and will 'reduce' the list to a single value. The supplied function takes an accumulator as its first value and the current element in the list as its second argument. The returned value from the function will be pass into the accumulator on the subsequent pass. + For example, say you wanted to add up a list of integers, your initial value would be `0` and your function would return the sum of the accumulator and the passed in element. var myList = [1,2,3,4].list() @@ -345,17 +473,151 @@ For example, say you wanted to add up a list of integers, your initial value wou ####foldRight(initialValue)(function(e, acc)) + List[A].foldRight(initialValue: B)(fn: (element: A, acc: B) -> B): B + Performs a fold right across the list. Similar to `foldLeft` except the supplied function is first applied to the right most side of the list. -####append(list2) +####append *alias: concat()* + + List[A].append(list: List[A]) : List[A] + +Will append the second list to the current list. Both list must be of the same type. -Will append the second list to the current list. +For example: var list1 = [1,2,3].list() var list2 = [4,5,6].list() var list3 = list1.append(list2) // list3.toArray() == [1,2,3,4,5,6] +####sequenceMaybe() : Maybe + + List[Maybe[A]].sequenceMaybe(): Maybe[List[A]] + +Takes a list of `Maybe`s and turns it into a `Maybe` `List`. If the list contains at least one `None` value then a `None` will be returned, otherwise a `Some` will be returned with a list of all the values. + +For example: + + var sequenced = [Some(1), Some(2), Some(3)].list().sequenceMaybe() + // sequenced == Some([1,2,3]) <- That's an immutable list not an array + + var sequenced = [Some(1), Some(2), None, Some(3), None].list().sequenceMaybe() + // sequenced == None + +####sequenceValidation() : Validation +Takes a list of `Validation`s and turns it into a `Validation` `List`. It will collect all the `success` values into a list on the `Success` side of the validation or it accumulates the errors on the `Failure` side, if there are **any** failures. + + List[Validation[E,A]].sequenceValidation(): Validation[List[E], List[A]] + + var sequenced = ["a".success(), "b".success(), "c".success()] + .list().sequenceValidation() + // sequenced == Success(["a", "b", "c"]) + + var sequenced = ["a".success(), + "b".success(), + "c".fail(), + "d".fail(), + "e".success()] + .list().sequenceValidation() + // sequenced == Fail(["c","d"]) + +####reverse + + List[A].reverse(): List[A] + +Returns a new list reversed. + + var list = [1,2,3].list().reverse() + // list.toArray() == [3,2,1] + + +## Non Empty Lists + +Much like the immutable list, a Non Empty List can never be empty. It implements the `comonad` pattern. It has a guaranteed head (total) +and a guaranteed (total) tail. + +#### Creating a NonEmptyList + + var nonEmptyList = NonEmptyList(1, [2,3,4].list()) + // alias + var nonEmptyList = NEL(1, [2,3,4].list()) + // or + var nonEmptyList = NonEmptyList(1, Nil) + // or fromList which returns a Maybe[NonEmptyList]. + var maybeNonEmptyList = NonEmptyList.fromList([1,2,3,4].list()) + +Trying to create an empty `NonEmptyList` will throw an exception. + +###Functions +####map + + NEL[A].map(fn: A -> B): NEL[B] + +Maps a function over a NonEmptyList. + +###bind *alias: flatMap, chain* + + NEL[A].bind(fn: A -> NEL[B]): NEL[B] + +Performs a monadic bind over the NonEmptyList. + +####head *alias: copure, extract* + + NEL[A].head(): A + +Returns the head of the NonEmptyList. Also known as `copure` or `extract` this is part of the comonad pattern. + +####tail + + NEL[A].tail(): List[A] + +Returns the tail of the `NonEmptyList`. + +####tails *alias: cojoin* + + NEL[A].tails(): NEL[NEL[A]] + +Returns all the tails of the `NonEmptyList`. Also known as `cojoin` this is part of the comonad pattern. A list is considered +a tail of itself. + +For example: + + NEL(1, [2,3,4].list()).tails() + //result: [ + // [ 1, 2, 3, 4 ], + // [ 2, 3, 4 ], + // [ 3, 4 ], + // [ 4 ] + // ] + +####mapTails *alias: cobind, coflatMap* + + NEL[A].mapTails(fn: NEL[A] -> B): NEL[B] + +Maps a function over the tails of the `NonEmptyList`. Also known as `cobind` this is part of the comonad pattern. + +For example: + + nonEmptyList.cobind(function (nel) { + return nel.foldLeft(0)(function(a,b){ + return a+b + }) + } + //result: [10,9,7,4] + +####reverse + + NEL[A].reverse(): NEL[A] + +Reverses the `NonEmptyList`. + +####fromList + + NEL.fromList(List[A]): Maybe[NEL[A]] + +Returns an optional `NonEmptyList`. If the supplied `List` is empty the result will be a `None`, otherwise a `NonEmptyList` wrapped in +a `Some` (or `Just`). + ##Other useful functions ###Functions ####fn.compose(f1) *alias fn.o(fn1)* diff --git a/bower.json b/bower.json index 96dffb4..8799db1 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "monet", - "version": "0.6.4", + "version": "0.6.5", "main": ["src/main/javascript/monet.js","src/main/javascript/monet-pimp.js"], "ignore": [ "**/.*", diff --git a/src/main/javascript/monet-pimp.js b/src/main/javascript/monet-pimp.js index e3a4e19..8df7d9e 100644 --- a/src/main/javascript/monet-pimp.js +++ b/src/main/javascript/monet-pimp.js @@ -1,4 +1,4 @@ -// monet-pimp.js 0.6.4 +// monet-pimp.js 0.6.5 // This file needs to be included after monet.js diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index d185b63..f1eb2be 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -1,4 +1,4 @@ -// Monet.js 0.6.4 +// Monet.js 0.6.5 // (c) 2012-2014 Chris Myers // Monet.js may be freely distributed under the MIT license.