diff --git a/README.md b/README.md index 440cb9d..e7266fd 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Using [bower]: or to install a specific version - bower install monet#0.8.5 + bower install monet#0.8.6 ### Node installation Using [npm]: @@ -46,7 +46,7 @@ Using [npm]: or to install a specific version - npm install monet@0.8.5 + npm install monet@0.8.6 ## A note on types @@ -1127,8 +1127,8 @@ Written and maintained by Chris Myers [@cwmyers](http://twitter.com/cwmyers). Fo [functionalJava]: http://functionaljava.org/ -[gitZip]: https://github.com/cwmyers/monet.js/archive/{{ page.version }}.zip -[gitTar]: https://github.com/cwmyers/monet.js/archive/{{ page.version }}.tar.gz +[gitZip]: https://github.com/cwmyers/monet.js/archive/0.8.5.zip +[gitTar]: https://github.com/cwmyers/monet.js/archive/0.8.5.tar.gz [bower]: http://bower.io [npm]: https://www.npmjs.com/ [scalaz]: https://github.com/scalaz/scalaz diff --git a/bower.json b/bower.json index 6bcf3a0..82fbdb5 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "monet", - "version": "0.8.5", + "version": "0.8.6", "main": ["src/main/javascript/monet.js","src/main/javascript/monet-pimp.js"], "ignore": [ "**/.*", diff --git a/component.json b/component.json index a090e60..0c27f97 100644 --- a/component.json +++ b/component.json @@ -1,6 +1,6 @@ { "name": "monet", - "version": "0.8.5", + "version": "0.8.6", "scripts": [ "src/main/javascript/monet.js", "src/main/javascript/monet-pimp.js" diff --git a/package.json b/package.json index e717687..b9cefe3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Chris Myers", "name": "monet", "description": "Monadic types library for JavaScript", - "version": "0.8.5", + "version": "0.8.6", "homepage": "https://github.com/cwmyers/monet.js", "repository": { "type": "git", diff --git a/src/main/javascript/monet-pimp.js b/src/main/javascript/monet-pimp.js index d960ce9..bee7fd4 100644 --- a/src/main/javascript/monet-pimp.js +++ b/src/main/javascript/monet-pimp.js @@ -1,4 +1,4 @@ -// monet-pimp.js 0.8.5 +// monet-pimp.js 0.8.6 // This file needs to be included after monet.js diff --git a/src/main/javascript/monet.js b/src/main/javascript/monet.js index 63b087d..8a3679a 100644 --- a/src/main/javascript/monet.js +++ b/src/main/javascript/monet.js @@ -1,4 +1,4 @@ -// Monet.js 0.8.5 +// Monet.js 0.8.6 // (c) 2012-2014 Chris Myers // Monet.js may be freely distributed under the MIT license. @@ -127,12 +127,6 @@ return list.foldRight(type.of(Nil))(type.map2(cons)) } - var lazySequence = function (list, type) { - return list.foldRight(type.of(function () { - return Nil - }))(type.map2(cons)) - } - var sequenceValidation = function (list) { return list.foldLeft(Success(Nil))(function (acc, a) { return acc.ap(a.map(function (v) { @@ -235,17 +229,14 @@ return sequence(this, Either) }, sequenceIO: function () { - return lazySequence(this, IO) + return sequence(this, IO) }, sequenceReader: function () { - return lazySequence(this, Reader) + return sequence(this, Reader) }, sequence: function (monadType) { return sequence(this, monadType) }, - lazySequence: function (monadType) { - return lazySequence(this, monadType) - }, head: function () { return this.head_ }, @@ -299,6 +290,10 @@ return new NEL.fn.init(head, tail) } + NEL.of = function(a) { + return NEL(a, Nil) + } + NEL.fn = NEL.prototype = { init: function (head, tail) { if (head == undefined || head == null) { @@ -462,6 +457,12 @@ return function (fn) { return self.isSome() ? fn(self.val) : defaultValue } + }, + filter: function(fn) { + var self = this + return self.flatMap(function(a) { + return fn(a) ? self : None() + }) } }; @@ -607,8 +608,10 @@ return new IO.fn.init(effectFn) } - IO.of = function (fn) { - return IO(fn) + IO.of = function (a) { + return IO(function() { + return a + }) } IO.fn = IO.prototype = { @@ -716,8 +719,14 @@ return new Reader.fn.init(fn) } - Reader.of = function (fn) { - return Reader(fn) + Reader.of = function (x) { + return Reader(function (_) { + return x + }) + } + + Reader.ask = function () { + return Reader(idFunction) } Reader.fn = Reader.prototype = { @@ -746,6 +755,12 @@ return Reader(function (config) { return fn(self.run(config)) }) + }, + local: function(fn) { + var self = this + return Reader(function(c) { + return self.run(fn(c)) + }) } } diff --git a/src/test/javascript/list_spec.js b/src/test/javascript/list_spec.js index dc5e103..024c301 100644 --- a/src/test/javascript/list_spec.js +++ b/src/test/javascript/list_spec.js @@ -169,13 +169,13 @@ describe("An immutable list", function () { var io1 = IO(function () { return "hi" }) - expect([io1].list().lazySequence(IO).run().toArray()).toEqual(["hi"]) + expect([io1].list().sequence(IO).run().toArray()).toEqual(["hi"]) }) it("with two IOs", function () { var io1 = IO(function () { return "hi" }) - expect([io1, io1].list().lazySequence(IO).run().toArray()).toEqual(["hi", "hi"]) + expect([io1, io1].list().sequence(IO).run().toArray()).toEqual(["hi", "hi"]) }) }) describe("of Readers", function () { @@ -183,7 +183,7 @@ describe("An immutable list", function () { var r = Reader(function (config) { return config.text }) - expect([r].list().lazySequence(Reader).run({text: "Hi Reader"}).toArray()).toEqual(["Hi Reader"]) + expect([r].list().sequence(Reader).run({text: "Hi Reader"}).toArray()).toEqual(["Hi Reader"]) }) it("with two Readers", function () { @@ -193,7 +193,7 @@ describe("An immutable list", function () { var r2 = Reader(function (config) { return config.name }) - expect([r1, r2].list().lazySequence(Reader).run({text: "Hi Reader", name: "Tom"}).toArray()).toEqual(["Hi Reader", "Tom"]) + expect([r1, r2].list().sequence(Reader).run({text: "Hi Reader", name: "Tom"}).toArray()).toEqual(["Hi Reader", "Tom"]) }) }) diff --git a/src/test/javascript/maybe_spec.js b/src/test/javascript/maybe_spec.js index 5f786e5..1d646ed 100644 --- a/src/test/javascript/maybe_spec.js +++ b/src/test/javascript/maybe_spec.js @@ -58,6 +58,12 @@ describe('A Maybe', function () { it('will return the first monad on orElse', function () { expect(someString.orElse(none)).toBeSomeMaybeWith("abcd") }) + it('will return a none on a failed filter', function() { + expect(someString.filter(function(a) {return a === "123"})).toBeNoneMaybe() + }) + it('will return a some on a successful filter', function() { + expect(someString.filter(function(a) {return a === "abcd"})).toBe(someString) + }) }) describe('without a value', function () { @@ -97,6 +103,9 @@ describe('A Maybe', function () { it('will return the supplied monad on orElse', function () { expect(none.orElse(someString)).toBeSomeMaybeWith('abcd') }) + it('will always return a None on filter', function() { + expect(none.filter(function(a){return true})).toBeNoneMaybe() + }) }) describe('Some constructed without a value', function () { diff --git a/src/test/javascript/monad_laws.js b/src/test/javascript/monad_laws.js new file mode 100644 index 0000000..6a88944 --- /dev/null +++ b/src/test/javascript/monad_laws.js @@ -0,0 +1,63 @@ +describe('The Monad', function () { + + var test = function(monad, reduction) { + var fa = function (x) { + return monad.of(x + 1) + } + + it("must obey left identity", function() { + + var f = reduction(monad.of(123).bind(fa)) + var fapply = reduction(fa(123)) + expect(f).toBe(fapply) + }) + + it("must obey right identity", function() { + var m = monad.of(123) + var m1 = m.bind(function (x) { return monad.of(x)}) + + expect(reduction(m)).toBe(reduction(m1)) + + }) + + it("must be associative", function() { + var m = monad.of(123) + var a = (m.bind(function(x) { return monad.of(x*2)})).bind(fa) + var b = (m.bind(function(x) { return monad.of(x*2).bind(fa)})) + expect(reduction(a)).toBe(reduction(b)) + + }) + } + + describe('Maybe', function() { + test(Maybe, function(m) {return m.some()}) + }) + + describe('Either', function() { + test(Either, function(m) {return m.right()}) + }) + + describe('IO', function() { + test(IO, function(m) {return m.run()}) + }) + describe('Reader', function() { + test(Reader, function (m) {return m.run()}) + }) + + describe('List', function() { + test(List, function(m) {return m.head()}) + }) + + describe('NonEmptyList', function() { + test(NEL, function(m) {return m.head()}) + }) + + describe('Free', function() { + test(Free, function(m) {return m.run()}) + }) + + describe('Validation', function() { + test(Validation, function(m) {return m.success()}) + }) + +}) diff --git a/src/test/javascript/nel_spec.js b/src/test/javascript/nel_spec.js index 025c96d..aec5ba6 100644 --- a/src/test/javascript/nel_spec.js +++ b/src/test/javascript/nel_spec.js @@ -16,7 +16,12 @@ 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 () { + var plus = function (a, b) { + return a + b + } + + + it("cannot be create with zero elements", function () { expect(function () { new NEL() }).toThrow("Cannot create an empty Non-Empty List.") @@ -95,7 +100,11 @@ describe("A Non-Empty immutable list", function () { }) it("can be reduced using reduceLeft", function () { - expect(nonEmptyList.reduceLeft(function (a,b) {return a+b})).toEqual(10) + expect(nonEmptyList.reduceLeft(plus)).toEqual(10) + }) + + it("single element NEL will reduce to the single element", function () { + expect(NEL(1, Nil).reduceLeft(plus)).toEqual(1) }) diff --git a/src/test/javascript/reader_spec.js b/src/test/javascript/reader_spec.js index ac407d8..87029cc 100644 --- a/src/test/javascript/reader_spec.js +++ b/src/test/javascript/reader_spec.js @@ -7,7 +7,8 @@ describe("A Reader Monad", function () { } var config2 = { url: "http://test2.com", - port: 8081 + port: 8081, + key: "not so secret" } var connect = function (endpoint, config) { return "POST " + config.url + ":" + config.port + "/" + endpoint @@ -49,4 +50,23 @@ describe("A Reader Monad", function () { }) + it("must support local", function () { + var reader = connect("something").flatMap(function (connectionString) { + return Reader(function (key) { + return connectionString + "?secretKey=" + key + }).local(function(c) {return c.key}) + }) + + expect(reader.run(config1)).toBe("POST http://test1.com:8080/something?secretKey=s3cr3t") + + }) + + it("can ask for config", function() { + var reader = Reader.ask().map(function(config) { + return config.key + }) + expect(reader.run(config1)).toBe("s3cr3t") + + }) + }) \ No newline at end of file