--- #theme: seriph canvasWidth: 750 title: Really Functional Data Structures author: Marvin Borner colorSchema: light class: text-center transition: instant mdc: true # take snapshot for each slide in the overview # overviewSnapshots: true --- # *Really* Functional
Data Structures [Marvin Borner](https://marvinborner.de) --- # Goal/Motivation - Represent arbitrary data using only functions - No classes, structs, numbers, etc. - Basically pure lambda calculus - Think/program more functionally - Elegant and minimal solutions - Useful for theorem proving? - Really fun! --- # Anonymous Functions (Lambdas) - Functions (abstractions) have an argument and a body - Applying a function with an argument substitutes it - Functions can be assigned to names (easier to read) --- ## JavaScript Notation ```js {monaco-run} {showOutputAt:1} f = x => x + 42 console.log(f(2)) ```
```js {monaco-run} {showOutputAt:3} f = x => y => x + y console.log(f(2)(42)) ```
--- # Really Functional Data Structures - Only use pure, closed terms - Multiple states are encoded via (unapplied) arguments and applications - Carefully, such that data doesn't reduce itself! - State can often be extracted using "selector" argument --- layout: cover --- # Common --- # Boolean Logic - **Capacity**: 1 Bit (true/false) - **Operations**: and/not/etc. --- ## Church Booleans ```js tru = t => f => t fls = t => f => f ``` --- ## Church Booleans ```js {monaco-run} {showOutputAt:'+1'} tru = t => f => t fls = t => f => f evalBool = bool => bool("true")("false") console.log(evalBool(tru)) console.log(evalBool(fls)) ``` --- ## Example: Negation - We know: `bool = t => f => t/f` - If `bool = t => f => t`, then `!bool = t => f => f` - If `bool = t => f => f`, then `!bool = t => f => t` - Therefore: ``` js negate = bool => t => f => bool(f)(t) // ^ ^ // one will be eliminated! ``` --- ## Example: Negation ```js {monaco-run} {showOutputAt:'+1'} tru = t => f => t fls = t => f => f negate = bool => t => f => bool(f)(t) evalBool = bool => bool("true")("false") console.log(evalBool(negate(tru))) console.log(evalBool(negate(fls))) ``` --- ## Other Operators - ``` js and = a => b => b(a)(b) ``` - ``` js xor = a => b => b(a(fls)(b))(a) ``` - ``` js xnor = a => b => b(a)(a(b)(tru)) ``` - ``` js impl = a => b => a(b)(tru) ``` - ... --- # Church Pairs - Stores two values - The selector function gets applied to both values: ``` js examplePair = s => s(A)(B) ``` --- ## Construction/Selection ```js {monaco-run} {showOutputAt:'+1'} cons = a => b => s => s(a)(b) car = pair => pair(a => b => a) cdr = pair => pair(a => b => b) // ^^^^^^^^^^^ // selector function examplePair = cons("a")("b") console.log(car(examplePair)) console.log(cdr(examplePair)) ``` --- layout: cover --- # Lists --- # Church Lists - Idea: A list is just a composition of pairs:
`[A, B, C, D] = (A, (B, (C, (D, NIL))))` - But what is `NIL`? - marks the end of the list - differentiable from other elements
--- ## `isNil`? - We define NIL such that it *ignores* its selector argument ```js cons = a => b => s => s(a)(b) nil = s => x => x exampleList = cons("a")(cons("b")(cons("c")(nil))) // = s1 => s1("a")(s2 => s2("b")(s3 => s3("c")(nil))) ``` --- ## `isNil`? - We define NIL such that it *ignores* its selector argument ```js {monaco-run} {showOutputAt:'+1'} cons = a => b => s => s(a)(b) nil = s => x => x exampleList = cons("a")(cons("b")(cons("c")(nil))) // = s1 => s1("a")(s2 => s2("b")(s3 => s3("c")(nil))) isNil = list => list(head => tail => right => fls)(tru) // ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ // cons/nil pair selector nil selector console.log(evalBool(isNil(nil))) console.log(evalBool(isNil(exampleList))) ``` --- ## Example: Iteration ```js {monaco-run} {showOutputAt:'+1'} y = f => x => f(y(f))(x) exampleList = cons("a")(cons("b")(cons("c")(nil))) length = y(rec => n => list => isNil(list) (() => n) (() => rec(n + 1)(cdr(list))())) (0) console.log(length(exampleList)()) ``` --- # Other Lists Example: Encoding of `["a", "b"]` - Parigot: ```js end => s1 => s2("a")(s2 => s2("b")(end)) ``` - Scott: ```js s1 => end1 => s1("a")(s2 => end2 => s2("b")(s3 => end3 => end3)) ``` - $n$-Tuple: ```js s => s("a")("b")... ``` --- layout: cover --- # Numbers (brief overview) --- # Church Numerals - Idea: Represent a number $n$ by applying $n$ composed functions to some argument! --- # Church Numerals - Idea: Represent a number $n$ by applying $n$ composed functions to some argument! - For example: ```js {monaco-run} {showOutputAt:'+1'} three = s => z => s(s(s(z))) evalChurch = n => n(x => x + 1)(0) console.log(evalChurch(three)) ``` --- ## Successor - Add another `s` to the composition - We also need to "rebind" existing `s` and `z`! --- ## Successor - Add another `s` to the composition - We also need to "rebind" existing `s` and `z`! ```js {monaco-run} {showOutputAt:'+1'} // three = s => z => s(s(s(z))) zero = s => z => z succ = n => s => z => s(n(s)(z)) console.log(evalChurch(succ(succ(succ(zero))))) ``` --- ## `isZero`? Similar to `isNil`! ```js {monaco-run} {showOutputAt:'+1'} // three = s => z => s(s(s(z))) isZero = n => n(z => fls)(tru) console.log(evalBool(isZero(zero))) console.log(evalBool(isZero(succ(zero)))) ``` --- # Other Numeral Systems Example: Encoding of $3$ - Scott: ```js s1 => z1 => s1(s2 => z2 => s2(s3 => z3 => s3(s4 => z4 => z4))) ``` - Parigot: ```js end => s1 => s1(s2 => s2(s3 => s3(s4 => end))) ``` - Mogensen: ```js end => b1 => b0 => b1(b1(end)) // binary end => tn => tp => t0 => t0(tp(end)) // balanced ternary ``` - Wadsworth, de Bruijn, Rationals, ... --- layout: cover --- # Algebraic Types --- # Products/Records - Stores multiple elements, supports construction and extraction - For two elements: Just a Church pair - For multiple elements: Extend the pair! --- # Products/Records - Stores multiple elements, supports construction and extraction - For two elements: Just a Church pair - For multiple elements: Extend the pair! ```js {monaco-run} {showOutputAt:'+1'} // data Friends = Friends { best :: String, friendly :: String, weird :: String } Friends = best => friendly => weird => s => s(best)(friendly)(weird) best = friends => friends(best => _ => _ => best) friendly = friends => friends(_ => friendly => _ => friendly) weird = friends => friends(_ => _ => weird => weird) friends = Friends("Alice")("Bob")("Carol") console.log(best(friends)) ``` --- # Sums/Unions - Similar: Stores multiple *types*, but only one at a time - Typical functional data structure (e.g. Haskell's `data` "|") --- ```js {monaco-run} // data Tree = Leaf Int | Node Tree Tree Leaf = n => leaf => node => leaf(n) Node = a => b => leaf => node => node(a)(b) nodeLeft = node => node(_ => _)(a => b => a) nodeRight = node => node(_ => _)(a => b => b) leafValue = leaf => leaf(n => n)(_ => _) isLeaf = tree => tree(n => tru)(a => b => fls) isNode = tree => tree(n => fls)(a => b => tru) ``` --- ```js {monaco-run} // data Tree = Leaf Int | Node Tree Tree Leaf = n => leaf => node => leaf(n) Node = a => b => leaf => node => node(a)(b) nodeLeft = node => node(_ => _)(a => b => a) nodeRight = node => node(_ => _)(a => b => b) leafValue = leaf => leaf(n => n)(_ => _) isLeaf = tree => tree(n => tru)(a => b => fls) isNode = tree => tree(n => fls)(a => b => tru) exampleTree = Node(Leaf(1))(Node(Leaf(2))(Leaf(3))) console.log(evalBool(isNode(exampleTree))) console.log(evalBool(isLeaf(nodeLeft(exampleTree)))) console.log(leafValue(nodeLeft(exampleTree))) console.log(leafValue(nodeRight(nodeRight(exampleTree)))) ``` --- layout: cover --- # Trees Trivial with presented data structures - Rose trees - Binary trees - Finger trees - Balanced trees --- layout: cover --- # Monads --- # Maybe Stores either nothing or a value, but *tagged* ```js {monaco-run} {showOutputAt:'+1'} // data Maybe a = Nothing | Just a Nothing = nothing => just => nothing Just = v => nothing => just => just(v) // instance Monad pure = Just bind = mx => f => mx(mx)(f) // ------------^^ ^----------------- // case Nothing case Just (apply) evalMaybe = maybe => maybe("Nothing")(v => "Just " + v) console.log(evalMaybe(bind(Nothing)(n => pure(n + 1)))) console.log(evalMaybe(bind(Just(42))(n => pure(n + 1)))) ``` --- # Either Stores either a value or another value, but *tagged* ```js {monaco-run} {showOutputAt:'+1'} // data Either a b = Left a | Right b Left = a => left => right => left(a) Right = b => left => right => right(b) // instance Monad pure = Right bind = mx => f => mx(Left)(f) // ---------^^^^ ^------------------ // case Left case Right (apply) evalEither = either => either(a => "Left " + a)(b => "Right " + b) console.log(evalEither(bind(Left(42))(n => pure(n + 1)))) console.log(evalEither(bind(Right(42))(n => pure(n + 1)))) ``` --- layout: cover --- # Meta (little detour) --- # Mogensen-Scott - Meta encoding of lambda terms - Tagged union: `Symbol x | Application Term Term | Lambda Term` - Translation: ```js enc[x] = sym => app => lam => sym(x) enc[f(x)] = sym => app => lam => app(enc[f])(enc[x]) enc[x => m] = sym => app => lam => lam(x => enc[m]) ``` --- ## Meta-Circular Interpreter Evaluate lambda terms using the lambda implementation of the language itself! ```js enc[x] = sym => app => lam => sym(x) enc[f(x)] = sym => app => lam => app(enc[f])(enc[x]) enc[x => m] = sym => app => lam => lam(x => enc[m]) ``` ```js eval = term => term (x => x) (f => x => eval(f)(eval(x))) (m => x => eval(m(x))) ``` --- # de Bruijn-Church - Idea: Encode symbols as Church-encoded de Bruijn indices - Abstractions are simpler and allow open terms - Translation: ```js enc[i] = idx => app => lam => church[idx] enc[f(x)] = idx => app => lam => app(enc[f])(enc[x]) enc[b] = idx => app => lam => lam(enc[b]) ``` --- ## 194 bit self interpreter Minimal data structures allow minimal interpreters! ``` 01010001 00011100 11010000 ###### 11100110 10000 ############ 00001 01011 ##### ##### 00001 11100 #### #### 00101 01110 #### ##### 00011 00000 #### ###### 10100 00011 #### ### #### 00111 10000 #### ## #### 11111 00001 #### ### #### 11110 00010 ###### #### 11110 10011 ##### #### 10100 11110 #### #### 00011 11000 ##### ##### 00011 11000 ############ 01011 01101110 ###### 00011001 00011010 00011010 ``` See [Metaprogramming and Self-Interpretation](https://text.marvinborner.de/2023-09-03-21.html) --- layout: cover class: text-left --- # Other Data? - Strings? List of 2-ary numerals. - Bits? List of Church booleans. - Maps? Balanced tree with Church pairs. --- layout: cover --- # Images --- # Quad Trees - Leafs/pixels: ```js black = w => b => b white = w => b => w ``` - 4-tuple (product type): ```js screen = s => s(TL)(TR)(BL)(BR) ``` - Where `TL`,`TR`,`BL`,`BR` are either a screen or a pixel --- layout: cover --- # Demo [Lambda Screen](https://lambda-screen.marvinborner.de)