Skip to content

Instantly share code, notes, and snippets.

@leandromoreira
Last active December 24, 2016 19:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leandromoreira/9504733c7f8c6361c46270ea953d8409 to your computer and use it in GitHub Desktop.
Save leandromoreira/9504733c7f8c6361c46270ea953d8409 to your computer and use it in GitHub Desktop.
// 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
@lfilho
Copy link

lfilho commented Dec 24, 2016

That was cool! Thanks for writing & sharing it 👍 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment