From 521895d137558cbe46a2c9ff0677dad4709fb95f Mon Sep 17 00:00:00 2001 From: bskyb-myersch Date: Wed, 9 Oct 2013 11:13:50 +0100 Subject: [PATCH 1/4] Separating pimping function on object to monad-pimp.js --- example/IO.html | 1 + example/List.html | 1 + src/main/javascript/monad-pimp.js | 31 +++++++++++++++++++++++++++++++ src/main/javascript/monad.js | 14 +------------- 4 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 src/main/javascript/monad-pimp.js diff --git a/example/IO.html b/example/IO.html index 1af5cae..f040f65 100644 --- a/example/IO.html +++ b/example/IO.html @@ -2,6 +2,7 @@ + Example IO page diff --git a/example/List.html b/example/List.html index 3e39fc7..c5b65fd 100644 --- a/example/List.html +++ b/example/List.html @@ -2,6 +2,7 @@ + Example IO page diff --git a/src/main/javascript/monad-pimp.js b/src/main/javascript/monad-pimp.js new file mode 100644 index 0000000..d39660a --- /dev/null +++ b/src/main/javascript/monad-pimp.js @@ -0,0 +1,31 @@ +// monad-pimp.js 0.6.0 + +// This file needs to be included after monad.js + +// (c) 2012-2013 Chris Myers +// Monad.js may be freely distributed under the MIT license. +// For all details and documentation: +// http://cwmyers.github.com/monad.js + + +(function (window) { + + Object.prototype.cons = function (list) { + return list.cons(this) + } + + Object.prototype.some = Object.prototype.just = function () { + return new Some(this) + } + + Object.prototype.success = function () { + return Validation.success(this) + } + + Object.prototype.fail = function () { + return Validation.fail(this) + } + + return this + +})(window || this); \ No newline at end of file diff --git a/src/main/javascript/monad.js b/src/main/javascript/monad.js index 4370ebf..17d9755 100644 --- a/src/main/javascript/monad.js +++ b/src/main/javascript/monad.js @@ -146,9 +146,7 @@ return l } - Object.prototype.cons = function (list) { - return list.cons(this) - } + /* Maybe Monad */ @@ -219,9 +217,6 @@ Some.fn.init.prototype = Some.fn - Object.prototype.some = Object.prototype.just = function () { - return new Some(this) - } var None = Nothing = Maybe.Nothing = Maybe.None = Maybe.none = Maybe.nothing = window.None = function () { return new None.fn.init() @@ -315,9 +310,6 @@ Success.fn.init.prototype = Success.fn; - Object.prototype.success = function () { - return Validation.success(this) - } var Fail = Validation.Fail = Validation.fail = function (error) { return new Fail.fn.init(error) @@ -362,10 +354,6 @@ Fail.fn.init.prototype = Fail.fn; - Object.prototype.fail = function () { - return Validation.fail(this) - } - var Semigroup = window.Semigroup = {} Semigroup.append = function (a, b) { From c355e71fa45e04853643b31d7562c149b9768b8c Mon Sep 17 00:00:00 2001 From: bskyb-myersch Date: Wed, 9 Oct 2013 11:59:25 +0100 Subject: [PATCH 2/4] Added pimp library --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 11918d6..8795098 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "monad.js", - "version": "0.6.0", - "main": "src/main/javascript/monad.js", + "version": "0.6.1", + "main": ["src/main/javascript/monad.js","src/main/javascript/monad-pimp.js"], "ignore": [ "**/.*", "node_modules", From c6c054758a7940e2e8a5cd5395a0fd8f84dc15d6 Mon Sep 17 00:00:00 2001 From: bskyb-myersch Date: Wed, 9 Oct 2013 12:02:43 +0100 Subject: [PATCH 3/4] Copied in the latest documentation --- README.md | 381 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 378 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7011c3f..96341b0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +--- +title: Home +layout: index +version: 0.6.1 +dev-version: 0.6.1 +--- + # monad.js [![Build Status](https://travis-ci.org/cwmyers/monad.js.png)](https://travis-ci.org/cwmyers/monad.js) @@ -5,7 +12,375 @@ For people who wish they didn't have to programme in JavaScript. Full documentation at http://cwmyers.github.com/monad.js/ -Inspired by projects such as [FunctionalJava][functionalJava] which incorporates monadic functional types into Java, monad.js -seeks to do the same for JavaScript. -[functionalJava]:http://functionaljava.org +## 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. + +This library is inspired by those that have come before, especially the [FunctionalJava][functionalJava] and [Scalaz][scalaz] projects. + +While functional programming may be alien to you, this library is a simple way to introduce monads and pure functional programming into your daily practises. + +##Download + +Download the [zip][gitZip] or [tar][gitTar] ball. + +##Source code + +The source is available at: [http://github.com/cwmyers/monad.js](http://github.com/cwmyers/monad.js). + +##Installation + +Simply download and add to your html pages or we also support [bower]. You can also include `monad-pimp.js` which contains extra functions on the `Object.prototype` +for creating monads. + + + + + +### Bower installation +Using [bower]: + + bower install monad.js + +or to install a specific version + + bower install monad.js#{{ page.version }} + +## Maybe + +The `Maybe` type is the most common way of represented the `null` type with making the possibilities of `NullPointer` +issues disappear. + +`Maybe` is effectively abstract and as two concrete subtypes: `Some` (also `Just`) and `None` (also `Nothing`). + +#### Creating an Maybe + + var maybe = Maybe.some(val); + var maybe = Maybe.none(); + var maybe = Maybe.fromNull(val); // none if val is null, some otherwise + +or more simply with the pimped method on Object. + + var maybe = "hello world".some() + 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`. + + maybe.map(fn) : Maybe + +For example: + + maybe.some(123).map(function(val) { + return val+1 + }) + => 124 + +#### bind(fn) *alias: flatMap* +`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: + + maybe.bind(function(val) { + if (val == "hi") { + return maybe.some("world") + } else { + return maybe.none() + } + }) + + +#### isSome() *alias: isJust* +`isSome` on a `Some` value will return `true` and will return `false` on a `None`. + + maybe.some("hi").isSome() + => true + + +#### isNone() *alias: isNothing* +`isNone` on a `None` value will return `true` and will return `false` on a `Some`. + +####some() *alias: just* +`some` will 'reduce' the `Maybe` to its value. + + Maybe.some("hi").some() + => "hi" + +####orSome(value) *alias: orJust* +Will return the containing value inside the `Maybe` or return the supplied value. + + maybe.some("hi").orSome("bye") + => "hi" + 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`. + + maybe.ap(maybeWithfn): 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. + +Here is an example for creating a string out of the result of a couple of `Maybe`s. We use `curry()` which is a pimped method on Function so we can partially apply. + + var person = function (forename, surname, address) { + return forename + " " + surname + " lives in " + address + }.curry() + + var maybeAddress = Maybe.just('Dulwich, London') + var maybeSurname = Maybe.just('Baker') + var maybeForename = Maybe.just('Tom') + + var personString = maybeAddress.ap(maybeSurname.ap(maybeForename.map(person))).just() + + // result: "Tom Baker lives in Dulwich, London" + +For further reading see [this excellent article](http://learnyouahaskell.com/functors-applicative-functors-and-monoids). + + +## 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. + +#### Creating a Validation + + var success = Validation.success(val); + var failure = Validation.fail("some error"); + +or with pimped methods on an object + + var success = val.success(); + 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`. + +####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.bind(fn) : validation + +For example: + + validation.bind(function(val) { + if (val == "hi") { + return Validation.success("world") + } else { + return Validation.fail("wow, you really failed.") + } + }) + + +####isSuccess() +Will return `true` if this is a successful validation, `false` otherwise. + +####isFail() +Will return `false` if this is a failed validation, `true` otherwise. + +####success() +Will return the successful value. + +####fail() +Will return the failed value, usually an error message. + +####ap(Validation(fn)) + +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. + + var person = function (forename, surname, address) { + return forename + " " + surname + " lives at " + address + }.curry(); + + + var validateAddress = Validation.success('Dulwich, London') + var validateSurname = Validation.success('Baker') + var validateForename = Validation.success('Tom') + + var personString = validateAddress.ap(validateSurname + .ap(validateForename.map(person))).success() + + // result: "Tom Baker lives at Dulwich, London" + + var result = Validation.fail(["no address"]) + .ap(Validation.fail(["no surname"]) + .ap(validateForename.map(person))) + // result: Validation(["no address", "no surname"]) + +####cata(failFn,successFn) + +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. + + var result = v.cata(function(failure) { + return "oh dear it failed because " + failure + }, function(success) { + return "yay! " + success + }) + +## IO +The `IO` monad is for isolating effects to maintain referential transparency in your software. Essentially you create a description of your effects of which is performed as the last action in your programme. The IO is lazy and will not be evaluated until the `perform` (*alias* `run`) method is called. + +#### Creating an IO + + var ioAction = IO(function () { return $("#id").val() }) + +###Functions +####IO(fn) *alias: io* +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* +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) +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. + + function() { return $("#id") }.io() + +####fn.io1() +Returns a function that will return an `IO` when one parameter is supplied. + + function(id) { return $(id) }.io1() + +or more simply + + $.io1() + +### Examples +Say we have a function to read from the DOM and a function to write to the DOM. *This example uses jQuery.* + + var read = function(id) { + return $(id).text() + } + + var write = function(id, value) { + $(id).text(value) + } + +On their own both functions would have a side effect because they violate referential transparency. The `read` function is dependent on an ever changing DOM and thus subsequent calls to it would not produce the same result. The `write` function obviously mutates the DOM and so it too is not referentially transparent, as each time it is called, an effect occurs. + +We can modify this functions so that instead of performing these side-effects they will just return an `IO` with the yet-to-be-executed function inside it. + + var read = IO(function (id) { return $(id).text() }) + + var write = function(id) { + return IO(function(value) { + $(id).text(value) + }) + } + +You can call `write(id)` until you are blue in the face but all it will do is return an `IO` with a function inside. + +We can now call `map` and `flatMap` to chain this two effects together. Say we wanted to read from a `div` covert all the text to uppercase and then write back to that `div`. + + var toUpper = function (text) { return text.toUpperCase() } + var changeToUpperIO = read("#myId").map(toUpper).flatMap(write("#myId")) + +So what is the type of `changeToUpperIO`? Well it is the `IO` type. And that means at this stage, **nothing has been executed yet**. The DOM has not been read from, the text has not been mapped and the DOM has not been updated. What we have is a **referentially transparent description** of our programme. + +In other pure functional languages such as Haskell we would simply return this type back to the runtime, but in JavaScript we have to manage this ourselves. So now let's run our effect. + + changeToUpperIO.run() + +Now our DOM should be updated with the text converted to upper case. + +It becomes much clearer which functions deal with IO and which functions simply deal with data. `read` and `write` return an `IO` effect but `toUpper` simply converts a supplied string to upper case. This pattern is what you will often find in your software, having an effect when you start (i.e. reading from a data source, network etc), performing transformations on the results of that effect and finally having an effect at the end (such as writing result to a database, disk, or DOM). + +##Other useful functions +###Functions +####fn.compose(f1) *alias fn.o(fn1)* +Function composition. `f.compose(g)` is equivalent to: + function compose(x) { + return f(g(x)) + } +####fn.andThen(fn1) +Function composition flipped. `f.andThen(g)` is equivalent to: + function compose(x) { + return g(f(x)) + } + +## Immutable lists + +An immutable list is a list that has a head element and a tail. A tail is another list. The empty list is represented by the `Nil` constructor. An immutable list is also known as a "cons" list. Whenever an element is added to the list a new list is created which is essentially a new head with a pointer to the existing list. + +#### Creating a list + +The easiest way to create a list is with the pimped method on Array. + + var myList = [1,2,3].list() + +which is equivalent to: + + var myList = List(1, List(2, List(3, Nil))) + +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` will prepend the element to the front of the list and return a new list. The existing list remains unchanged. + + var newList = myList.cons(4) + // newList.toArray() == [4,1,2,3] + // myList.toArray() == [1,2,3] + +`cons` is also available as a pimped method on `Object.prototype`: + + var myList = ["a","b","c"].list() + var newList = "z".cons(myList) + newList.toArray() == ["z","a","b","c"] + +####map(fn) + +Maps the supplied function over the list. + + var list = [1,2,3].list().map(function(a) { + return a+1 + }) + // list == [2,3,4] + +####flatMap(fn) *alias: bind()* + +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` 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() + var sum = myList.foldLeft(0)(function(acc, e) { + return e+acc + }) + // sum == 10 + +####foldRight(initialValue)(function(e, acc)) + +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) + +Will append the second list to the current list. + + 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] + + + +[functionalJava]: http://functionaljava.org/ +[gitZip]: https://github.com/cwmyers/monad.js/zipball/master (zip format) +[gitTar]: https://github.com/cwmyers/monad.js/tarball/master (tar format) +[bower]: http://bower.io +[scalaz]: https://github.com/scalaz/scalaz From 71882040839e9223a3749d43564714c2fdb70e68 Mon Sep 17 00:00:00 2001 From: bskyb-myersch Date: Wed, 9 Oct 2013 12:04:41 +0100 Subject: [PATCH 4/4] Copied in the latest documentation --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 96341b0..4ef7143 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,3 @@ ---- -title: Home -layout: index -version: 0.6.1 -dev-version: 0.6.1 ---- - # monad.js [![Build Status](https://travis-ci.org/cwmyers/monad.js.png)](https://travis-ci.org/cwmyers/monad.js) @@ -49,7 +42,7 @@ Using [bower]: or to install a specific version - bower install monad.js#{{ page.version }} + bower install monad.js#0.6.1 ## Maybe