|
// This post will briefly explain (omiting, skipping some parts) in code what is |
|
// Functor, Pointed Functor, Monad and Applicative Functor. Maybe by reading the |
|
// code you will easily grasp these functional concepts. |
|
|
|
// if you only want to run this code go to: |
|
// https://jsfiddle.net/leandromoreira/buq5mnyk/ |
|
// or https://gist.github.com/leandromoreira/9504733c7f8c6361c46270ea953d8409 |
|
|
|
// This code requires you to have require.js loaded (or you can load ramda instead :P) |
|
requirejs.config({ |
|
paths: { |
|
ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min' |
|
}, |
|
}); |
|
|
|
require(['ramda'], function(_) { |
|
// First let's create a Container that is a type that holds (wraps) a value, a useful abstraction to handle state. |
|
var Container = function(x) { |
|
this.__value = x; |
|
} |
|
// of is a method to create Container of x type |
|
Container.of = function(x) { |
|
return new Container(x); |
|
}; |
|
console.log("should be 3", Container.of(3)) |
|
|
|
// We can improve this building block (Container) by providing a way to handle the wrapped value, |
|
// this is basically a Functor, which is a type that implements map (it is mappable) and obeys some laws. |
|
// By the way a Pointed Functor is a functor with an of method. |
|
Container.prototype.map = function(f) { |
|
return Container.of(f(this.__value)); |
|
} |
|
|
|
var c4 = Container.of(4) |
|
var inc = function(x) { |
|
return x + 1 |
|
} |
|
var c5 = c4.map(inc) |
|
// We first created a container of 4 then we map a increase over it resulting in a container of 5 |
|
console.log("should be 5", c5) |
|
|
|
// Maybe is a functor that checks if the value is null/undefined |
|
// it is useful to avoid erros like "Cannot read property x of null" |
|
Container.prototype.isNothing = function() { |
|
return (this.__value === null || this.__value === undefined); |
|
}; |
|
// Now our map will also check weather it's valid or not. |
|
Container.prototype.map = function(f) { |
|
return this.isNothing() ? Container.of(null) : Container.of(f(this.__value)); |
|
}; |
|
|
|
var address = function(person) { |
|
return person.address; |
|
}; |
|
var upperCase = function(t) { |
|
return t.toUpperCase() |
|
} |
|
// Although we're passing an invalid value to the container it won't broke |
|
console.log("should be null without errors", Container.of(null).map(address).map(upperCase)) |
|
// but when we do pass the right parameter it produces the expected output |
|
console.log("should be HERE", Container.of({ |
|
name: "Diddy", |
|
address: "here" |
|
}).map(address).map(upperCase)) |
|
// this is good but a failing error with no message can make things worst 😦 |
|
|
|
// This functions maps any function a functor |
|
var map = _.curry(function(ordinaryFn, functor) { |
|
return functor.map(ordinaryFn); |
|
}); |
|
|
|
var aFunctor = Container.of(2) |
|
var sum6 = function(x) { |
|
return x + 6 |
|
} |
|
// given an ordinary function and an functor it produces another functor |
|
var plus6 = map(sum6) |
|
var y = plus6(aFunctor) |
|
console.log("should be a Functor of 8", y) |
|
|
|
// Either is a functor that can return two types either Right (normal flow) or Left (some error occorred). |
|
// Now here what is great is that we can say what was the error. |
|
var Left = function(x) { |
|
this.__value = x; |
|
}; |
|
Left.of = function(x) { |
|
return new Left(x); |
|
}; |
|
Left.prototype.map = function(f) { |
|
return this; |
|
}; |
|
var Right = function(x) { |
|
this.__value = x; |
|
}; |
|
Right.of = function(x) { |
|
return new Right(x); |
|
}; |
|
Right.prototype.map = function(f) { |
|
return Right.of(f(this.__value)); |
|
} |
|
|
|
console.log("should be 10", Right.of(8).map(inc).map(inc)) |
|
console.log("should be unchaged 8", Left.of(8).map(inc).map(inc)) |
|
|
|
var nonNegative = function(x) { |
|
if (x < 0) { |
|
return Left.of("you must pass a positive number") |
|
} else { |
|
return Right.of(x) |
|
} |
|
} |
|
|
|
console.log("should be 10", nonNegative(9).map(inc)) |
|
console.log("should be an error message", nonNegative(–4).map(inc)) |
|
|
|
// IO is a functor that holds functions as values, and instead of mapping the value |
|
// it'll map functions and compose them like a array of functions. |
|
var IO = function(f) { |
|
this.__value = f; |
|
}; |
|
IO.of = function(x) { |
|
return new IO(function() { |
|
return x; |
|
}); |
|
}; |
|
IO.prototype.map = function(f) { |
|
return new IO(_.compose(f, this.__value)); |
|
}; |
|
|
|
var composedLazyFunctions = IO.of(3).map(inc).map(inc).map(inc) |
|
console.log("this is a lazy composed function", composedLazyFunctions) |
|
console.log("this is the execution of that composed function", composedLazyFunctions.__value()) |
|
|
|
var readFile = function(filename) { |
|
return new IO(function() { |
|
return "read file from " + filename |
|
}); |
|
}; |
|
var print = function(x) { |
|
return new IO(function() { |
|
return x |
|
}); |
|
}; |
|
// Cat will be a composed function that produces and IO of an IO :X |
|
var cat = _.compose(map(print), readFile) |
|
|
|
var catGit = cat('.git/config') |
|
|
|
console.log("it should be an IO of IO IO(IO())", catGit) |
|
// This creates an awkward situation where if we want the real value we need to |
|
// catGit.__value().__value() how about create a join that unwraps the value. |
|
IO.prototype.join = function() { |
|
return this.__value() |
|
}; |
|
console.log("should be 'read file from .git/config'", catGit.join().join()) |
|
|
|
// Notice that we still need to call join twice, and if we join every time we map? |
|
// this is what we know was chain |
|
var chain = _.curry(function(ordinaryFn, functor) { |
|
return functor.map(ordinaryFn).join(); |
|
}); |
|
|
|
var complexSum = function(initialNumber) { |
|
return new IO(function() { |
|
var x = initialNumber * 4 |
|
var y = x * 4 |
|
return (y + 42) – x * 4 |
|
}); |
|
}; |
|
|
|
var incIO = function(x) { |
|
return new IO(function() { |
|
return x + 1 |
|
}); |
|
}; |
|
|
|
var doubleIO = function(x) { |
|
return new IO(function() { |
|
return x * 2 |
|
}); |
|
}; |
|
|
|
var cleverMath = _.compose( |
|
chain(doubleIO), |
|
chain(incIO), |
|
chain(incIO), |
|
complexSum |
|
); |
|
|
|
var multiplier = Math.floor((Math.random() * 552) + 7) |
|
var ordinaryValue = Math.floor((Math.random() * 98134123) – 12) |
|
|
|
var cleverMathResult = cleverMath(ordinaryValue * multiplier) |
|
|
|
console.log("should be 88", cleverMathResult.join()) |
|
// Monads are pointed functors that can flatten 🙂 |
|
|
|
// Now let's finish with an Applicative Functor which is a pointed functor with an ap(ply) method |
|
Container.prototype.ap = function(other_container) { |
|
return other_container.map(this.__value) |
|
} |
|
console.log("should be Container(4)", Container.of(inc).ap(Container.of(3))) |
|
}) |
|
// Please consider to read these links bellow |
|
// http://www.leonardoborges.com/writings/2012/11/30/monads-in-small-bites-part-i-functors/ |
|
// https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch8.html |