Last active
December 24, 2016 19:32
-
-
Save leandromoreira/9504733c7f8c6361c46270ea953d8409 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
That was cool! Thanks for writing & sharing it 👍 👏