Functional Patterns for the non-mathematician

Post on 14-Jun-2015

2.215 views 0 download

Tags:

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 of Functional Patterns for the non-mathematician

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