millionways

129
A Million Ways to Fold in JS

Upload: brian-lonsdorf

Post on 15-Jul-2015

526 views

Category:

Technology


0 download

TRANSCRIPT

A Million Ways to

Fold in JS

Agenda

[Recursion, Corecursion, Transducers, Monoids, F-Algebras]

for (i = 0; i < xs.length; i++) {

xs[i] += 1;

}

Recursion

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

Base Case

ActionRecursion

var reverse = function(xs) {

if(xs.length === 0) return [];

return reverse(rest(xs)).concat(first(xs));

}

var reverse = function(xs) {

if(xs.length === 0) return [];

return reverse(rest(xs)).concat(first(xs));

}

Base Case

ActionRecursion

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

sum([1,2,3])

// 1 + sum([2,3])

// 1 + (2 + sum([3]))

// 1 + (2 + (3 + sum([])))

// 1 + (2 + (3 + 0))

// 1 + (2 + 3)

// 1 + 5

//=> 6

ES6

ES6

ES6!

Err, i mean 2015

Tail Recursive

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

RangeError: Maximum call stack size exceeded

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

var sum = function(list) {

function go(acc, xs) {

if(xs.length === 0) return acc;

return go(acc+first(xs), rest(xs));

}

return go(0, list)

}

var sum = function(xs) {

if(xs.length === 0) return 0;

return first(xs) + sum(rest(xs));

}

var sum = function(list) {

function go(acc, xs) {

if(xs.length === 0) return acc;

return go(acc+first(xs), rest(xs));

}

return go(0, list)

}

var reduce = function(f, acc, xs) {

if(xs.length === 0) return acc;

return reduce(f, f(acc, first(xs)), rest(xs));

}

var reduce = function(f, acc, xs) {

if(xs.length === 0) return acc;

return reduce(f, f(acc, first(xs)), rest(xs));

}

var sum = function(xs){

return reduce((acc, x) => x + acc, 0, xs)

}

var reverse = function(xs) {

return reduce((acc, x) => [x].concat(acc), [], xs)

}

var map = function(f, xs) {

return reduce((acc, x) => acc.concat(f(x)), [], xs)

}

var filter = function(f, xs) {

return reduce((acc, x) => f(x) ? acc.concat(x) : acc, [], xs)

}

Catamorphism

+

any

all

size

max

min

sortBy

find

groupBy

first

last

take

drop

etc…

Any loop can be captured with a fold!

Apomorphism

Paramorphism

var para = function(f, acc, xs) {

if(xs.length === 0) return acc;

return para(f, f(acc, first(xs), xs), rest(xs));

}

var para = function(f, acc, xs) {

if(xs.length === 0) return acc;

return para(f, f(acc, first(xs), xs), rest(xs));

}

HORS

HORS

Agenda

[Corecursion, Transducers, Monoids, F-Algebras]

Recursion

Anamorphism

var unfold = function(f, seed) {

function go(f, seed, acc) {

var res = f(seed);

return res ? go(f, res[1], acc.concat([res[0]])) : acc;

}

return go(f, seed, [])

}

unfold(x => if(x < 26) [String.fromCharCode(x+65), x+1], 0);

//=> [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z]

var range = function(i, count) {

return unfold(x => if(x <= count) [x, x+1], i);

}

range(5,10)

//=> [ 5, 6, 7, 8, 9, 10 ]

var tree = Node(Node(Leaf(2), 1, Leaf(3)), 0, Leaf(4))

fold((acc, x) => acc + x, 0, tree)

//=> 10

var clicks = $("#red-button").asEventStream("click")

fold((acc, el) => acc.append(el.id), $("#clicklog"), clicks)

//=> <div id=“clicklog”/>

var us = {

“391": "keith@suburban",

“52": “[email protected]"

}

fold((acc, addr) => acc.cc(addr), Email.create(), us)

//=> Email

Cons(3, Cons(4, Cons(5, Nil)));

tail3 tail4 tail5 Nil

Linked List

var Nil = {}

var _Cons = function(h, tl) {

this.head = h;

this.tail = tl;

};

var Cons = function(h, tl) { return new _Cons(h, tl) }

var lst = Cons(3, Cons(4, Cons(5, Nil)));

1

Node(Node(Leaf(2), 1, Leaf(3)), 0, Leaf(4))

2 3

645

7

Tree

var Empty = {}

var _Leaf = function(x) { this.x = x; }

var Leaf = function(x) { return new _Leaf(x) }

var _Node = function(l, x, r) {

this.left = l;

this.x = x;

this.right = r;

}

var Node = function(l, x, r) { return new _Node(l, x, r) }

Agenda

[Transducers, Monoids, F-Algebras]

Recursion

Corecursion

var map = function(f, xs) {

return reduce((acc, x) => concat(acc, f(x)), [], xs)

}

var map = function(f, xs) {

return reduce((acc, x) => concat(acc, f(x)), [], xs)

}

Accumulation

Transformation Iteration

var map = function(f, xs) {

return reduce((acc, x) => concat(acc, f(x)), [], xs)

}

var mapper = function(f) {

return (acc, x) => concat(acc, f(x))

}

reduce(mapper(x => x + 1), [], [1,2,3])

//=> [2,3,4]

var mapper = function(f) {

return (acc, x) => concat(acc, f(x))

}

reduce(mapper(x => x + 1), [], [1,2,3])

//=> [2,3,4]

var mapper = function(f, cnct) {

return (acc, x) => cnct(acc, f(x))

}

reduce(mapper(x => x + 1, concat), [], [1,2,3])

//=> [2,3,4]

var mapper = function(f, cnct) {

return (acc, x) => cnct(acc, f(x))

}

reduce(mapper(x => x + 1, concat), [], [1,2,3])

//=> [2,3,4]

var filterer = function(f, cnct) {

return (acc, x) => f(x) ? cnct(acc, x) : acc

}

reduce(filterer(x => x > 1, concat), [], [1,2,3])

//=> [2,3]

var filterer = function(f, cnct) {

return (acc, x) => f(x) ? cnct(acc, x) : acc

}

reduce(filterer(x => x > 1, concat), [], [1,2,3])

//=> [2,3]

var filterer = function(f, cnct) {

return (acc, x) => f(x) ? cnct(acc, x) : acc

}

reduce(filterer(x => x > 1, concat), [], [1,2,3])

//=> [2,3]

var copy = function(xs) {

return reduce(concat, [], xs)

}

filterer(x => x > 1, mapper(x => x + 1, concat))

//=> (acc, x) => x > 1 ? concat(acc, f(x)) : acc

filterer(x => x > 1, mapper(x => x + 1, concat))

//=> (acc, x) => x > 1 ? concat(acc, f(x)) : acc

reduce(filterer(x => x > 1,

mapper(x => x + 1, concat)),

[], [1,2,3])

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, concat)),

[], [1,2,3])

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, concat)),

[], [1,2,3])

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, append)),

Nil, Cons(1, Cons(2, Cons(3, Nil))))

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, append)),

Nil, Cons(1, Cons(2, Cons(3, Nil))))

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, insert)),

Empty, Node(Node(Leaf(2), 1, Leaf(3)), 0, Leaf(4)))

//=> [3,4]

reduce(filterer(x => x > 1,

mapper(x => x + 1, insert)),

Empty, Node(Node(Leaf(2), 1, Leaf(3)), 0, Leaf(4)))

//=> [3,4]

Iteration

Transformation

Accumulation

Agenda

[Monoids, F-Algebras]

Recursion

Corecursion

Transducers

// left identity

concat([], xs) == xs

// right identity

concat(xs, []) == xs

// associativity

concat(concat(xs, ys), zs) == concat(xs, concat(ys, zs))

var sum = function(xs) {

return reduce((acc, x) => acc + x, 0, xs)

}

var _Sum = function(x) { this.val = x }

var Sum = function(x){ return new _Sum(x) }

_Sum.prototype.concat = function(y){

return Sum(this.val + y.val)

}

_Sum.prototype.empty = function(){ return Sum(0) }

var sum = function(xs) {

return reduce((acc, x) => acc + x, 0, xs)

}

_Sum.prototype.concat = function(y){

return Sum(this.val + y.val)

}

_Sum.prototype.empty = function(){ return Sum(0) }

var sum = function(xs) {

return reduce((acc, x) => acc + x, 0, xs)

}

fold([Sum(1), Sum(2), Sum(3), Sum(4)])

//=> Sum(10)

_Sum.prototype.concat = function(y){

return Sum(this.val + y.val)

}

_Sum.prototype.empty = function(){ return Sum(0) }

fold([Product(1),Product(2),Product(3),Product(4)])

//=> Product(24)

_Product.prototype.concat = function(y){

return Product(this.val * y.val)

}

_Product.prototype.empty = function(){ return Product(1) }

fold([Max(11),Max(16),Max(3),Max(9)])

//=> Max(16)

_Max.prototype.concat = function(y){

return Max(this.val > y.val ? this.val : y.val)

}

_Max.prototype.empty = function(){ return Max(-Infinity) }

_All.prototype.concat = function(y){

return Any(this.val && y.val)

}

_All.prototype.empty = function(){ return All(true) }

fold([All(false), All(false), All(true), All(false)])

//=> All(false)

_Any.prototype.concat = function(y){

return Any(this.val || y.val)

}

_Any.prototype.empty = function(){ return Any(false) }

fold([Any(false), Any(false), Any(true), Any(false)])

//=> Any(true)

any

all

max

min

and

or

product

sum

find

contains

concat

toList

_Any.prototype.concat = function(y){

return Any(this.val || y.val)

}

_Any.prototype.empty = function(){ return Any(false) }

fold([Any(false), Any(false), Any(true), Any(false)])

//=> Any(true)

var foldMap = function(f,xs) {

return compose(fold, map(f))(xs);

}

var sum = function(xs){ return foldMap(Sum, xs).val }

var max = function(xs){ return foldMap(Max, xs).val }

var any = function(xs){ return foldMap(Any, xs).val }

var tree = Node(Node(Leaf(2), 1, Leaf(3)), 2, Leaf(4))

sum(tree)

//=> 12

product(tree)

//=> 38

max(tree)

//=> 4

var lst = Cons(true, Cons(false, Cons(true, Nil)))

any(lst)

//=> true

all(lst)

//=> false

Iteration

Transformation

Accumulation

[Sum(a)] -> Sum(a)

[Product(a)] -> Product(a)

[Max(a)] -> Max(a)

F(a) -> a

Agenda

[F-Algebras]

Recursion

Corecursion

Transducers

Monoids

No, not

That F

var cata = function(f, xs) {

return f(xs.map(ys => cata(f,ys)))

}

var cata = function(f, xs) {

return f(xs.map(ys => cata(f,ys)))

}

var cata = function(f, xs) {

return f(xs.map(ys => cata(f,ys)))

}

Fixed point of a Functor

Nil.map = function(f) { return Nil; }

_Cons.prototype.map = function(f) {

return Cons(this.head, f(this.tail))

}

var sum = function(x) {

return (x === Nil) ? 0 : x.head + x.tail

}

var lst = Cons(2, Cons(3, Cons(4, Nil)));

cata(sum, lst);

//=> 9

var sum = function(x) {

return (x === Nil) ? 0 : x.head + x.tail

}

var lst = Cons(2, Cons(3, Cons(4, Nil)));

cata(sum, lst);

//=> 9

Algebra

var sum = function(x) {

return (x === Nil) ? 0 : x.head + x.tail

}

var lst = Cons(2, Cons(3, Cons(4, Nil)));

cata(sum, lst);

// Nil

// Cons(4, 0)

// Cons(3, 4)

// Cons(2, 7)

//=> 9

map(x => x + 1, Cons(2, Cons(3, Cons(4, Nil))))

//=> Cons(3, Cons(4, Cons(5, Nil)))

var map = function(f, xs) {

return cata(x => (x == Nil) ? Nil : Cons(f(x.head), x.tail), xs)

}

Empty.map = function(f) { return Empty }

_Leaf.prototype.map = function(f) {

return Leaf(this.x)

}

_Node.prototype.map = function(f) {

return Node(f(this.left), this.x, f(this.right))

}

cata(t =>

switch (t.constructor) {

case _Node: return t.left + t.x + t.right;

case _Leaf: return t.x;

default: 0;

}, tr)

//=> 10

var tr = Node(Node(Leaf(2), 1, Leaf(3)), 0, Leaf(4))

var ana = function(g, a) {

return g(a).map(x => ana(g,x))

}

var ana = function(g, a) {

return g(a).map(x => ana(g,x))

}

var arrToList = function(xs) {

return xs.length === 0 ? Nil : Cons(first(xs), rest(xs))

}

ana(arrToList, [1,2,3,4,5])

//=> Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))

CoAlgebra

var arrToList = function(xs) {

return xs.length === 0 ? Nil : Cons(first(xs), rest(xs))

}

ana(arrToList, [1,2,3,4,5])

//=> Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))

Co-Scary

var arrToList = function(xs) {

return xs.length === 0 ? Nil : Cons(first(xs), rest(xs))

}

ana(arrToList, [1,2,3,4,5])

//=> Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))

var makeAlphabet = function(x) {

if(x > 25) return Nil;

return Cons(String.fromCharCode(x+65), x+1);

}

ana(makeAlphabet, 0)

//=> Cons(A, Cons(B, Cons(C, Cons(D, Cons(E, Cons(F, Cons(G, Cons(H, Cons(I, Cons(J, Cons(K, Cons(L, Cons(M, Cons(N, Cons(O,

var range = function(acc, count) {

return ana(x => (x >= count) ? Nil : Cons(x, x+1), acc)

}

range(2, 10)

//=> Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Cons(7, Cons(8,

Cons(9, Nil))))))))

Iteration

Transformation

Accumulation

Mul(Add(Const(2), Const(3)), Const(4))

Mul(Add(Const(2), Const(3)), Const(4))

var _Const = function(val) { this.val = val }

var Const = function(x) { return new _Const(x) }

var _Add = function(x, y) {

this.x = x;

this.y = y;

}

var Add = function(x, y) { return new _Add(x, y) }

var _Mul = function(x, y) {

this.x = x

this.y = y

}

var Mul = function(x, y) { return new _Mul(x, y) }

_Const.prototype.map = function(f) { return this }

_Add.prototype.map = function(f) {

return Add(f(this.x), f(this.y))

}

_Mul.prototype.map = function(f) {

return Mul(f(this.x), f(this.y))

}

var interpret = function(a) {

switch(a.constructor) {

case _Mul: return a.x * a.y;

case _Add: return a.x + a.y;

case _Const: return a.val;

}

}

var program = Mul(Add(Const(2), Const(3)), Const(4))

cata(interpret, program);

//=> 20

var _Concat = function(v, next) {

this.val = v;

this.next = next;

}

var Concat = function(v, x){ return new _Concat(v, x) }

var _Replace = function(v, x, next) {

this.val = v;

this.x = x;

this.next = next;

}

var Replace = function(v, x, nt){ return new _Replace(v, x, nt) }

var _Input = function(v) { this.val = v }

var Input = function(v){ return new _Input(v) }

_Concat.prototype.map = function(f) {

return Concat(this.val, f(this.next))

}

_Replace.prototype.map = function(f) {

return Replace(this.val, this.x, f(this.next))

}

_Input.prototype.map = function(f) {

return Input(this.val)

}

var interpret = function(t) {

switch (t.constructor) {

case _Concat: return t.next.concat(t.val);

case _Replace: return t.next.replace(t.val, t.x);

case _Input: return t.val;

}

}

var prog = Concat("world", Replace("h", "m", Input(“hello")))

cata(interpret, prog)

//=> melloworld

var interpret1 = function(t) {

switch (t.constructor) {

case _Concat:

return "concatting “+t.val+" after "+t.next;

case _Replace:

return "replacing "+t.val+" with "+t.x+" on "+t.next;

case _Input:

return t.val;

}

}

var prog = Concat("world", Replace("h", "m", Input(“hello")))

cata(interpret1, prog)

//=> concatting world after replacing h with m on hello

Agenda

Recursion

Corecursion

Transducers

Monoids

F-Alegbras

THanks!

@drbooleanhttps://github.com/DrBoolean/RecursionTalk