functional patterns for the non-mathematician
DESCRIPTION
Fluentconf 2014 talk: Functional design patterns such as lenses, arrows, functors, and monads all come from category theory. To fully grok them, you’ll probably have to wade through the whitest white papers, fighting the mathematical syntax and abstract examples. I’m hoping to demonstrate the ideas into javascript. I’ll be showing direct and practical applications for every day programming.TRANSCRIPT
Functional Patternsfor the non-mathematician
add(4, 2)
//=> 6
// associativeadd(add(1, 2), 4) == add(1, add(2, 4))
// commutativeadd(4, 1) == add(1, 4)
// identityadd(n, 0) == n
// distributivemultiply(2, add(3,4)) == add(multiply(2, 3), multiply(2, 4))
add(4.4, 2.2)
//=> 6.6
var inc = new Increaser(4);inc.increaseBy(2);inc.value();// 6
Interfaces
Properties/Laws
Polymorphic
Composable
Currying
var reverseCap = compose(capitalize, reverse)
reverseCap(“hello”)//=> “Olleh”
Composition
var reverseCap = compose(capitalize, reverse)
reverseCap(“hello”)//=> “Olleh”
Composition
var reverseCap = compose(capitalize, reverse)(“hello”)//=> “Olleh”
Composition“hello”
“olleh”
“Olleh”
compose(compose(f, g), h) == compose(f, compose(g, h))
Composition(associativity)
compose(f, g, h)
Composition(associativity)
var i = compose(g, h)compose(f, i)
Composition(associativity)
var getFromDb = compose(pluck('rows'), User.findAll)
var cleanUpData = compose(capitalize, pluck('name'))
var renderTemplate = TemplateEngine.render(‘users_table')
var makePage = compose(renderTemplate, map(cleanUpData), getFromDb)
makePage({limit: 20})
var getFromDb = compose(pluck('rows'), User.findAll)
var cleanUpData = compose(capitalize, pluck('name'))
var renderTemplate = TemplateEngine.render(‘users_table')
var makePage = compose(renderTemplate, map(cleanUpData), getFromDb)
makePage({limit: 20})Perfect world
function (property, x) { return x[property];}
Getters/Setters
function (property, value, x) { x[property] = value; return x;}
Lenses
over(l, f, x)
view(l, x)
set(l, y, x)
var user = {id: 1, name: ‘Alicia'}
var L = makeLenses([‘name’])
view(L.name, user) // 'Alicia'
set(L.name, 'Ally', user) // {id: 1, name: 'Ally'}
over(L.name, toUpperCase, user) // {id: 1, name: 'ALICIA'}
Lenses
var user = {id: 1, name: {first: ‘doris’, last: ‘day’ }}
var L = makeLenses([‘name’, ‘first’])
var firstNameChar = compose(L.name, L.first, _1)
over(firstNameChar, toUpperCase, user)//=> {id: 1, name: {first: ‘Doris’, last: ‘day’ }}
Lenses
view(l, set(l, b, a)) == b
set(l, view(l, a), a) == a
set(l, c, set(l, b, a)) == set(l, c, a)
Lens laws
if(x !== null && x !== undefined) { return f(x)}
Null checking
fmap(f, Maybe(x))
Null checking
var fmap = function(f, mappable) { return mappable.map(f)}
Null checking
fmap(function(x) { return x.toUpperCase() }, Maybe(‘hi’))//=> Maybe(‘HI’)
fmap(function(x) { return toUpperCase(x); }, Maybe(null))//=> Maybe(null)
Null checking
fmap(function(x) { return x.toUpperCase() }, Maybe(‘hi’))//=> Maybe(‘HI’)
fmap(function(x) { return x.toUpperCase() }, Maybe(null))//=> Maybe(null)
Null checking
compose(fmap(f), Maybe)
Null checking
var id = function(x) { return x; }
fmap(id, x) == id(x)
Fmap laws(identity)
compose(fmap(f), fmap(g)) == fmap(compose(f, g))
Fmap laws(composition)
if(x !== null && x !== undefined) { return f(x)} else { throw ‘Some Error!’}
Error Handling
Error Handling
fmap(f, Either(‘Some error’, x))
Either(‘need an int’, 3)//=> Right(3)
fmap(function(x) { return x + 1; }, Either(‘need an int’, undefined))//=> Left(‘need an int’)
Error Handling
Either(‘need an int’, 3)//=> Right(3)
Either(‘need an int’, undefined))//=> Left(‘need an int’)
Error Handling
fmap(function(x) { return x + 1; }, Right(2))//=> Right(3)
fmap(function(x) { return x + 1; }, Either(‘need an int’, undefined))//=> Left(‘need an int’)
Error Handling
fmap(function(x) { return x + 1; }, Right(2))//=> Right(3)
fmap(function(x) { return x + 1; }, Left(‘need an int’))//=> Left(‘need an int’)
Error Handling
compose(fmap(f), Either(‘error’))
Error Handling
f(x, function(y) {return g(y);
});
Future values
Future values
fmap(f, Promise(x))
var p = new Promise();fmap(function(x) { return log(reverse(x)) }, p)//=> Promise()
p.resolve([1,2,3])//=>[3, 2, 1]
Future values
Something that implements map
Functor
if(x !== null && x !== undefined) { var y = f(x) if(y !== null && y !== undefined) { return g(y) }}
Nesting
f(x, function(y) {return g(y, function(z) {
return h(z)})
})
Nesting
compose(mjoin, fmap(f))
Nesting
var getField = compose(Maybe, document.querySelector)var getValue = compose(Maybe, pluck(‘value’))
var greet = compose(fmap(fmap(concat(‘hello’))), fmap(getValue), getField)greet(‘#name’)//=> Maybe(Maybe(‘hello chris’))
var greet = compose(fmap(concat(‘hello’)), mjoin, fmap(getValue), getField)greet(‘#name’)//=> Maybe(‘hello chris’)
Nesting
var getField = compose(Maybe, document.querySelector)var getValue = compose(Maybe, pluck(‘value’))
var greet = compose(fmap(fmap(concat(‘hello’))), fmap(getValue), getField)greet(‘#name’)//=> Maybe(Maybe(‘hello chris’))
var greet = compose(fmap(concat(‘hello’)), mjoin, fmap(getValue), getField)greet(‘#name’)//=> Maybe(‘hello chris’)
Nesting
compose(mjoin, fmap(g), mjoin, fmap(f))
Nesting
mcompose(g, f)
compose(mjoin, fmap(g), mjoin, fmap(f))
Nesting
mcompose(g, f)
mcompose(mcompose(f, g), h) == mcompose(f, mcompose(g, h))
mcompose(f, M) == f
mcompose(M, f) == f
Monad laws
Multiple null argsvar notNull = function(x) { return x !== null && x !== undefined}
if(notNull(x) && notNull(y)) { return f(x, y)}
Multiple Async fn’svar y,z;
f(x, function(result) { y = result;
if(z) {return h(y, z)
})})
g(x, function(result) { z = result;
if(y) {return h(y, z)
})})
liftA2(f, A(x), A(y))
Multiple values
liftA3(f, A(x), A(y), A(z))
Multiple values
liftA2(add, Maybe(3), Maybe(4))//=> Maybe(7)
liftA2(add, Maybe(null), Maybe(4))//=> Maybe(null)
Multiple values
liftA2(add, Maybe(3), Maybe(4))//=> Maybe(7)
liftA2(add, Maybe(null), Maybe(4))//=> Maybe(null)
Multiple values
var tweets_p = Http.get(‘/twitter/tweets’)var photos_p = Http.get(‘/flickr/photos’)var makeCollage = _.curry(function (tweets, photos){})
liftA2(makeCollage, tweets_p, photos_p)
Multiple values
// identityap(A(id), m) == m
// compositionap(ap(ap(A(compose), f), g), w) == ap(f, ap(g, w))
// homomorphismap(A(f), A(x)) == A(f(x))
// interchangeap(u, A(x)) == ap(A(function(f) { return f(x); }), u)
Applicative laws
Accumulation
reduce(function(acc, x) {return acc + x;
}, 0, [1,2,3])
Accumulation
reduce(function(acc, x) {return acc * x;
}, 1, [1,2,3])
Accumulation
reduce(function(acc, x) {return acc || x;
}, false, [false, false, true])
Accumulation
reduce(function(acc, x) {return acc && x;
}, true, [false, false, true])
Accumulation
reduce(function(acc, x) {return acc > x ? acc : x;
}, 0, [12, 5, 35])
Monoid
mappend(m, m)
mempty(m)
mconcat([m])
Monoid
mappend(m, m)
mempty(m)
mconcat([m])
mconcat([Sum(1), Sum(2), Sum(3)])
//=> Sum(6)
Accumulation
mconcat([Product(1), Product(2), Product(3)])
//=> Product(6)
Accumulation
mconcat([Max(13), Max(2), Max(9)])//=> Max(13)
Accumulation
mconcat([Any(false), Any(false), Any(true)])//=> Any(true)
Accumulation
mconcat([All(false), All(false), All(true)])//=> All(false)
Accumulation
compose(mconcat, map(M))
Accumulation
// left identitymappend(mempty, x) == x
// right identitymappend(x, mempty) == x
// associativitymappend(mappend(x, y), z) == mappend(x, mappend(y,
z))
Monoid laws
Combinators
function(x) { return [f(x), g(x)]}
Combinators
function(x, y) { return [f(x), g(y)]}
compose(f, g)
ampersand(f, g)
asterisk(f, g)
first(f)
second(f)
Arrows
first(reverse)([‘Stan’, ‘Lee']) // [‘natS’, ‘Lee’]
second(reverse)([‘Stan’, ‘Lee']) // [‘Stan’, ‘eeL’]
ampersand(reverse, toUpperCase)(‘Stan’) // [‘natS’, ‘STAN’]
asterisk(reverse, toUpperCase)([‘Stan’, ‘Lee']) // [‘natS’, ‘LEE’]
Arrows
A(id) == id
A(compose(f, g)) == A(compose(f, A(g)))
first(A(f)) == A(first(f))
first(compose(f, g)) == compose(first(f), first(g))
compose(first(f), A(pluck(0))) == compose(A(pluck(0)), f)
compose(first(f), A(asterisk(id, g)) == compose(A(asterisk(id, g)), first(f))
compose(first(first(f)), A(assoc) == compose(A(assoc), first(f))
Arrow laws
Thanks!
@drboolean
https://github.com/DrBoolean/patterns_talk