├── 0001-expressing-side-effects-with-recipes ├── no-need-to-mock.png ├── package.json ├── example-moon.js ├── example-javascript.js ├── README.md └── package-lock.json ├── README.md ├── 0004-supercompilation-for-free ├── test_b.hs ├── test_c.hs ├── test_c.lam ├── test_a.hs ├── test_b.lam ├── test_a.lam ├── test_a.fm └── README.md ├── 0002-explaining-lambda-encodings-with-js ├── examples.js └── README.md ├── 0000-oracle-free-terms-are-turing-complete └── README.md └── 0003-theorem-proving-vs-testing └── README.md /0001-expressing-side-effects-with-recipes/no-need-to-mock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorTaelin/articles/HEAD/0001-expressing-side-effects-with-recipes/no-need-to-mock.png -------------------------------------------------------------------------------- /0001-expressing-side-effects-with-recipes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0001-implementing-side-effects-with-free-monads", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "example.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "moon-lang": "^0.1.22" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Articles 2 | 3 | - [Oracle-free terms are turing-complete.](0000-oracle-free-terms-are-turing-complete) 4 | 5 | - [Expressing side effects with Recipes.](0001-expressing-side-effects-with-recipes) 6 | 7 | - [Explaining λ-encodings with JS.](0002-explaining-lambda-encodings-with-js) 8 | 9 | - [Theorem proving vs testing.](0003-theorem-proving-vs-testing) 10 | 11 | - [Supercompilation for free.](0004-supercompilation-for-free) 12 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_b.hs: -------------------------------------------------------------------------------- 1 | apply_n_times n f x = go n x where { go 0 r = r; go n r = go (n - 1) (f r) } 2 | xor b = case b of { True -> not; False -> id } 3 | 4 | zip4 :: (a -> b -> c) -> (a,a,a,a) -> (b,b,b,b) -> (c,c,c,c) 5 | zip4 f (ax,ay,az,aw) (bx,by,bz,bw) = (f ax bx, f ay by, f az bz, f aw bw) 6 | 7 | main = do 8 | let a = (True, True, False, False) 9 | let b = (True, False, True, False) 10 | print $ apply_n_times (2 ^ 25) (zip4 xor a) b 11 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_c.hs: -------------------------------------------------------------------------------- 1 | import Prelude hiding (succ) 2 | 3 | apply_n_times n f x = go n x where { go 0 r = r; go n r = go (n - 1) (f r) } 4 | 5 | data Bin = O Bin | I Bin 6 | 7 | peek :: Int -> Bin -> String 8 | peek 0 xs  = "" 9 | peek n (O xs) = "0" ++ peek (n - 1) xs 10 | peek n (I xs) = "1" ++ peek (n - 1) xs 11 | 12 | succ :: Bin -> Bin 13 | succ (O xs) = I xs 14 | succ (I xs) = O (succ xs) 15 | 16 | zero :: Bin 17 | zero = O zero 18 | 19 | main = putStrLn $ peek 64 (apply_n_times (2 ^ 63) succ zero) 20 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_c.lam: -------------------------------------------------------------------------------- 1 | @apply_n_times #n #f #x ::n f x 2 | @peek #n #x :::n #t :t #x ::x #x #r #t ::t x #a #b #c :::r a b :a c #x #r #t ::t x #a #b #c :::r a b :b c #t ::t x #a #b #c c #x #r r 3 | @pow #n #m :m n 4 | @Y #f :#x :f :x x #x :f :x x 5 | @O #xs #O #I :O xs 6 | @I #xs #O #I :I xs 7 | 8 | @zero :Y #zero :O zero 9 | 10 | @succ :Y #succ #xs #O #I ::xs I #xs :O :succ xs 11 | 12 | @main ::peek 64 :::apply_n_times ::pow 2 63 succ zero 13 | 14 | main 15 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_a.hs: -------------------------------------------------------------------------------- 1 | apply_n_times n f x = go n x where { go 0 r = r; go n r = go (n - 1) (f r) } 2 | xor b = case b of { True -> not; False -> id } 3 | toList (x,y,z,w) = [x,y,z,w] 4 | fromList [x,y,z,w] = (x,y,z,w) 5 | 6 | zip4 :: (a -> b -> c) -> (a,a,a,a) -> (b,b,b,b) -> (c,c,c,c) 7 | zip4 f a b = fromList (zipWith f (toList a) (toList b)) 8 | 9 | main = do 10 | let a = (True, True, False, False) 11 | let b = (True, False, True, False) 12 | print $ apply_n_times (2 ^ 25) (zip4 xor a) b 13 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_b.lam: -------------------------------------------------------------------------------- 1 | @apply_n_times #n #f #x ::n f x 2 | @pow #n #m :m n 3 | @True #f #t t 4 | @False #f #t f 5 | @id #x x 6 | @not #b #f #t ::b t f 7 | @xor #b ::b not id 8 | 9 | @zip4 #f #a #b #tuple 10 | :a #ax #ay #az #aw 11 | :b #bx #by #bz #bw 12 | ::::tuple ::f ax bx ::f ay by ::f az bz ::f aw bw 13 | 14 | @main 15 | @a #tuple ::::tuple True True False False 16 | @b #tuple ::::tuple True False True False 17 | :::apply_n_times ::pow 2 25 ::zip4 xor a b 18 | 19 | main 20 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_a.lam: -------------------------------------------------------------------------------- 1 | @apply_n_times #n #f #x ::n f x 2 | @pow #n #m :m n 3 | @True #f #t t 4 | @False #f #t f 5 | @id #x x 6 | @not #b #f #t ::b t f 7 | @xor #b ::b not id 8 | @zipWith #f #xs #ys #C #N :::xs #x #xs #ys ::ys #y #ys ::C ::f x y :xs ys N #ys N ::ys #x #xs #C #N ::C x xs #C #N N 9 | @toList #t :t #a #b #c #d #C #N ::C a ::C b ::C c ::C d N 10 | @fromList #xs #t :::xs #x #xs #r :xs :r x #r r t 11 | @open #t #fn :t #a #b #c #d :fn #t ::::t a b c d 12 | 13 | @zip4 #f #a #b #t 14 | ::open a #a 15 | ::open b #b 16 | ::fromList :::zipWith f :toList a :toList b t 17 | 18 | @main 19 | @a #tuple ::::tuple True True False False 20 | @b #tuple ::::tuple True False True False 21 | :::apply_n_times ::pow 2 25 ::zip4 xor a b 22 | 23 | main 24 | -------------------------------------------------------------------------------- /0001-expressing-side-effects-with-recipes/example-moon.js: -------------------------------------------------------------------------------- 1 | const Moon = require("moon-lang"); 2 | 3 | // A Recipe for a program that tells the user his BMI 4 | const getUserBMIRecipe = Moon.parse(`prompt. alert. return. 5 | height: <(prompt "What is your height (in meters)?") 6 | weight: <(prompt "What is your weight (in kg)?") 7 | bmi: (div (stn weight) (mul (stn height) (stn height))) 8 | (alert (con "Your BMI is " (nts bmi)))> 9 | (return bmi) 10 | `); 11 | 12 | // Converts a Recipe into a mocked program 13 | const mock = recipe => recipe 14 | (text => cont => ([input,...rest]) => [["prompted", text]].concat(cont(input)(rest))) 15 | (text => cont => ([input,...rest]) => [["alerted", text]].concat(cont(input)(rest))) 16 | (result => ([input,...rest]) => [["returned", result]]); 17 | 18 | // Mocks the program above 19 | const mockedGetUserBMI = mock(getUserBMIRecipe); 20 | 21 | // Tests it with fake user inputs 22 | console.log(mockedGetUserBMI(["1.80", "70"])); 23 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/test_a.fm: -------------------------------------------------------------------------------- 1 | apply_n_times (n)=>(f)=>(x)=>n(f, x) 2 | pow (n)=>(m)=>m(n) 3 | True (f)=>(t)=>t 4 | False (f)=>(t)=>f 5 | id (x)=>x 6 | not (b)=>(f)=>(t)=>b(t,f) 7 | xor (b)=>b(not, id) 8 | zipWith (f)=>(xs)=>(ys)=>(C)=>(N)=>xs((x)=>(xs)=>(ys)=>ys((y)=>(ys)=>C(f(x,y),xs(ys)),N), (ys)=>N, ys((x)=>(xs)=>(C)=>(N)=>C(x,xs), (C)=>(N)=>N)) 9 | toList (t)=>t((a)=>(b)=>(c)=>(d)=>(C)=>(N)=>C(a,C(b,C(c,C(d ,N))))) 10 | fromList (xs)=>(t)=>xs((x)=>(xs)=>(r)=>xs(r(x)),(r)=>r,t) 11 | open (t)=>(fn)=>t((a)=>(b)=>(c)=>(d)=>fn((t)=>t(a,b,c,d))) 12 | zip4 (f)=>(a)=>(b)=>(t)=> open(a, (a)=> open(b, (b)=> fromList(zipWith(f,toList(a),toList(b)),t))) 13 | main 14 | let a = (tuple)=>tuple(True,True,False,False) 15 | let b = (tuple)=>tuple(True,False,True,False) 16 | let n25 = (s)=>(z)=>s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(z))))))))))))))))))))))))) 17 | let n2 = (s)=>(z)=>s(s(z)) 18 | apply_n_times(pow(n2,n25),zip4(xor,a),b) 19 | -------------------------------------------------------------------------------- /0001-expressing-side-effects-with-recipes/example-javascript.js: -------------------------------------------------------------------------------- 1 | // Converts a Recipe into an actual, side-effective program 2 | const run = ([request,params,cont]) => () => { 3 | switch (request) { 4 | case "return": return Promise.resolve(parmas); 5 | case "prompt": return Promise.resolve(run(cont(prompt(params[0])))()); 6 | case "alert": return Promise.resolve(run(cont(alert(params[0])))()); 7 | } 8 | }; 9 | 10 | // Converts a Recipe into a mocked program 11 | const mock = ([request,params,cont]) => ([input,...rest]) => { 12 | switch (request) { 13 | case "return": return [["returned", params]]; 14 | case "prompt": return [["prompted", params[0]]].concat(mock(cont(input))(rest)); 15 | case "alert": return [["alerted", params[0]]].concat(mock(cont())(rest)); 16 | } 17 | }; 18 | 19 | // A Recipe for a program that tells the user his BMI 20 | const getUserBMIRecipe = 21 | ["prompt", ["What is your height (in meters)?"], (height) => 22 | ["prompt", ["What is your weight (in kg)?"], (weight) => { 23 | var bmi = Number(weight) / (Number(height) * Number(height)); 24 | return ["alert", ["Your BMI is " + bmi], () => 25 | ["return", bmi] 26 | ]; 27 | }] 28 | ]; 29 | 30 | // Generates a side-effective program (works on the browser) 31 | const getUserBMI = run(getUserBMIRecipe); 32 | // getUserBMI(); // works on the browser 33 | 34 | // Generates a mocked program 35 | const mockedGetUserBMI = mock(getUserBMIRecipe); 36 | 37 | // Tests it with fake inputs 38 | console.log(mockedGetUserBMI(["1.80", "70"])); 39 | 40 | 41 | -------------------------------------------------------------------------------- /0002-explaining-lambda-encodings-with-js/examples.js: -------------------------------------------------------------------------------- 1 | True = True => False => True; 2 | False = True => False => False; 3 | 4 | show = bool => bool("True")("False"); 5 | print = bool => console.log(show(bool)); 6 | 7 | or = a => b => (a (b) (False)); 8 | and = a => b => (a (True) (b)); 9 | xor = a => b => (a (b (False) (True)) (b (True) (False))); 10 | 11 | print (or (True) (True)); 12 | print (or (True) (False)); 13 | print (and (True) (True)); 14 | print (and (True) (False)); 15 | print (xor (True) (True)); 16 | print (xor (True) (False)); 17 | 18 | 19 | 20 | 21 | Succ = nat => Succ => Zero => Succ (nat (Succ) (Zero)); 22 | Zero = Succ => Zero => Zero; 23 | 24 | show = nat => nat (nat => "(Succ " + nat + ")") ("Zero"); 25 | print = nat => console.log(show(nat)); 26 | 27 | add = a => b => a (Succ) (b); 28 | mul = a => b => a (b (Succ)) (Zero); 29 | 30 | isZero = a => a (a => "False") ("True"); 31 | 32 | c0 = Zero; 33 | c1 = Succ (c0); 34 | c2 = Succ (c1); 35 | c3 = Succ (c2); 36 | 37 | print (add (c3) (c2)); 38 | print (mul (c3) (c2)); 39 | console.log (isZero (c0)); 40 | console.log (isZero (c1)); 41 | 42 | 43 | 44 | 45 | Succ = nat => Succ => Zero => Succ (nat); 46 | Zero = Succ => Zero => Zero 47 | 48 | show = nat => nat (nat => "(Succ " + show(nat) + ")") ("Zero"); 49 | print = nat => console.log(show(nat)); 50 | 51 | foldNat = a => succ => zero => a (pred => succ (foldNat (pred) (succ) (zero))) (zero); 52 | 53 | add = a => b => foldNat (a) (Succ) (b); 54 | mul = a => b => foldNat (a) (foldNat (b) (Succ)) (Zero); 55 | 56 | isZero = a => a (a => "False") ("True"); 57 | 58 | c0 = Zero; 59 | c1 = Succ (c0); 60 | c2 = Succ (c1); 61 | c3 = Succ (c2); 62 | 63 | print (add (c3) (c2)); 64 | print (mul (c3) (c2)); 65 | console.log (isZero (c0)); 66 | console.log (isZero (c1)); 67 | -------------------------------------------------------------------------------- /0000-oracle-free-terms-are-turing-complete/README.md: -------------------------------------------------------------------------------- 1 | ## Quick review of Lamping's abstract algorithm 2 | 3 | There is an amazing algorithm capable of evaluating functional programs optimally, known as [Lamping's abstract algorithm](https://github.com/maiavictor/abstract-algorithm). It is extremely promising for being simple, efficient and inherently parallel. The main issue hindering its adoption is the unfortunate fact it doesn't work for all λ-terms. For some of them, it simply returns incorrect results. In order to solve this issue, Lamping, and many others, attempted to extend the original algorithm with a machinery known as "oracle". The problem is that, so far, every attempt at designing an oracle ruined the original performance of the algorithm. Mind this table: 4 | 5 | Term | GeomOpt | GeomImpl | IntComb | YALE | none 6 | -- | --- | --- | --- | --- | --- 7 | 22II | 204 | 56 | 66 | 38 | 21 8 | 222II | 789 | 304 | 278 | 127 | 50 9 | 3II | 75 | 17 | 32 | 17 | 9 10 | 33II | 649 | 332 | 322 | 87 | 49 11 | 322II | 7055 | 4457 | 3268 | 383 | 85 12 | 223II | 1750 | 1046 | 869 | 213 | 69 13 | 44II | 3456 | 2816 | 2447 | 148 | 89 14 | 15 | It shows the total number of [graph rewrites](https://github.com/MaiaVictor/abstract-algorithm/blob/master/images/combinators_rules.png?raw=true) required to evaluate a few λ-terms with existing oracles, with the last column showing the oracle-free version. As you can see, the difference is brutal, rendering the algorithm unpractical. Note that, while YALE, in some cases, has almost comparable performance in number of rewrites, it has very complex rules and, as such, would require way more instructions in practice. 16 | 17 | ## The oracle-free subset is turing-complete! 18 | 19 | There is, though, a huge subset of λ-terms that is computable without the oracle. What if this subset is sufficient? In other words, what if, instead of trying to adapt the algorithm to work with all λ-terms, we designed a type system that restricted outselves to that subset? Such proposal would raise 2 main questions: 20 | 21 | 1. Is this subset expressive enough to solve any problem? 22 | 23 | 2. Would such language allow for natural programming styles? 24 | 25 | We now can answer both questions. [This program](https://github.com/MaiaVictor/abstract-algorithm/blob/master/examples/lambda-calculus.js) implements an evaluator of arbitrary λ-terms *as a λ-term that can be evaluated on Lamping's abstract algorithm without the oracle*! It, thus, demonstrates that oracle-free terms are turing-complete. Note that this code is obfuscated and would look much cleaner in a suitable syntax. 26 | 27 | As such, yes, we could have a language based on this subset. That language would impose some restrictions at compile time, enabling your programs to be computed optimally and in parallel. If someone found those restrictions too bothersome, no problems: he/she can always fallback to a monadic DSL and use whatever programming style he/she prefers. 28 | -------------------------------------------------------------------------------- /0002-explaining-lambda-encodings-with-js/README.md: -------------------------------------------------------------------------------- 1 | Lambda encodings allow you to express Haskell algebraic datatypes in any language with closures. Here I'll explain them without many words, mostly examples. Carefully observe how one translates to the other and you'll get it. 2 | 3 | ## Booleans 4 | 5 | For non-recursive data-types, Church-encoding == Scott-encodding. 6 | 7 | - Haskell 8 | 9 | ```haskell 10 | data Bool' = True' | False' deriving Show 11 | 12 | and' a b = case a of 13 | True' -> b 14 | False' -> False' 15 | 16 | or' a b = case b of 17 | True' -> True' 18 | False' -> b 19 | 20 | xor' a b = case a of 21 | False' -> case b of 22 | False' -> False' 23 | True' -> True' 24 | True' -> case b of 25 | False' -> True' 26 | True' -> False' 27 | 28 | main = do 29 | print (or' True' True') 30 | print (or' True' False') 31 | print (and' True' True') 32 | print (and' True' False') 33 | print (xor' True' False') 34 | print (xor' True' True') 35 | ``` 36 | 37 | - JavaScript 38 | 39 | ```javascript 40 | True = True => False => True; 41 | False = True => False => False; 42 | 43 | show = bool => bool("True")("False"); 44 | print = bool => console.log(show(bool)); 45 | 46 | or = a => b => (a (b) (False)); 47 | and = a => b => (a (True) (b)); 48 | xor = a => b => (a (b (False) (True)) (b (True) (False))); 49 | 50 | print (or (True) (True)); 51 | print (or (True) (False)); 52 | print (and (True) (True)); 53 | print (and (True) (False)); 54 | print (xor (True) (True)); 55 | print (xor (True) (False)); 56 | ``` 57 | 58 | ## Nats 59 | 60 | For recursive data-types, Church-encoding represents structures as folds, Scott-encoding represents structures as pattern-matchs. Scott-encoding is directly equivalent to Haskell ADTs. Church-encoding is equivalent to a Haskell ADT with `fold` pre-applied to it. Scott-encoding is good because it allows O(1) pattern-matching, but it is bad because recursive algorithms need non-termination and it doesn't fuse. Church-encoding is good because it allows O(1) concatenation (monadic bind), but it is bad because pattern-matching is O(N). 61 | 62 | - Haskell 63 | 64 | ```haskell 65 | data Nat = Succ Nat | Zero deriving Show 66 | 67 | foldNat (Succ pred) succ zero = succ (foldNat pred succ zero) 68 | foldNat Zero succ zero = zero 69 | 70 | add a b = foldNat a Succ b 71 | mul a b = foldNat a (foldNat b Succ) Zero 72 | 73 | isZero a = case a of 74 | (Succ a) -> False 75 | Zero -> True 76 | 77 | c0 = Zero 78 | c1 = Succ c0 79 | c2 = Succ c1 80 | c3 = Succ c2 81 | 82 | main = do 83 | print (add c3 c2) 84 | print (mul c3 c2) 85 | print (isZero c0) 86 | print (isZero c1) 87 | ``` 88 | 89 | - JavaScript (Church-encoding) 90 | 91 | ```javascript 92 | Succ = nat => Succ => Zero => Succ (nat (Succ) (Zero)); 93 | Zero = Succ => Zero => Zero; 94 | 95 | show = nat => nat (nat => "(Succ " + nat + ")") ("Zero"); 96 | print = nat => console.log(show(nat)); 97 | 98 | add = a => b => a (Succ) (b); 99 | mul = a => b => a (b (Succ)) (Zero); 100 | 101 | isZero = a => a (a => "False") ("True"); 102 | 103 | c0 = Zero; 104 | c1 = Succ (c0); 105 | c2 = Succ (c1); 106 | c3 = Succ (c2); 107 | 108 | print (add (c3) (c2)); 109 | print (mul (c3) (c2)); 110 | console.log (isZero (c0)); 111 | console.log (isZero (c1)); 112 | ``` 113 | 114 | - JavaScript (Scott-encoding) 115 | 116 | ```javascript 117 | Succ = nat => Succ => Zero => Succ (nat); 118 | Zero = Succ => Zero => Zero 119 | 120 | show = nat => nat (nat => "(Succ " + show(nat) + ")") ("Zero"); 121 | print = nat => console.log(show(nat)); 122 | 123 | foldNat = a => succ => zero => a (pred => succ (foldNat (pred) (succ) (zero))) (zero); 124 | 125 | add = a => b => foldNat (a) (Succ) (b); 126 | mul = a => b => foldNat (a) (foldNat (b) (Succ)) (Zero); 127 | 128 | isZero = a => a (a => "False") ("True"); 129 | 130 | c0 = Zero; 131 | c1 = Succ (c0); 132 | c2 = Succ (c1); 133 | c3 = Succ (c2); 134 | 135 | print (add (c3) (c2)); 136 | print (mul (c3) (c2)); 137 | console.log (isZero (c0)); 138 | console.log (isZero (c1)); 139 | ``` 140 | 141 | ## Lists 142 | 143 | Check out [Moon-Base](https://github.com/MaiaVictor/moon-lang/tree/master/base), it is a growing library written in a subset of JS which has only functions, numbers, maps and strings. As such, most algorithms are λ-encoded. It currently has a bunch of Church-encoded list algorithms, and also merge-sort on Scott-encoded lists. Feel free to contribute. 144 | 145 | If you have any question about λ-encodings, feel free to open an issue on this repository. 146 | -------------------------------------------------------------------------------- /0001-expressing-side-effects-with-recipes/README.md: -------------------------------------------------------------------------------- 1 | ## Expressing side-effects with Recipes (and how that can be useful) 2 | 3 | One of the benefits of using a language free of side-effects such as Haskell is how easy it is to write tests. Since everything is just pure functions all the way down, you can test any program [by merely checking if it returns what you expect](https://stackoverflow.com/a/986737/1031791). This article will explain how arbitrary side-effective algorithms can be expressed purely, and how that can be useful in situations such as [when writing mocks and tests](no-need-to-mock.png). This technique applies to arbitrary languages, not only pure ones. 4 | 5 | Mind the following JavaScript program: 6 | 7 | ```javascript 8 | function getUserBMI() { 9 | return new Promise((resolve, reject) => { 10 | var height = prompt("What is your height (in meters)?"); 11 | var weight = prompt("What is your weight (in kg)?"); 12 | var bmi = Number(weight) / (Number(height) * Number(height)); 13 | alert("Your BMI is " + bmi + "."); 14 | return bmi; 15 | }); 16 | } 17 | ``` 18 | 19 | When executed, it asks the user his height/weight, tells him his body mass index and returns a Promise that resolves to it. Pretty basic. Problem is: how do we test it? Since it contains side-effective operations (`prompt` / `alert`), we need to build a mock. Mocking isn't widely regarded as the most satisfying occupation, but it could be avoided if `getUserBMI` was pure function. That might sound nonsense at first: `getUserBMI` requires user input, so it can't be pure! This is correct, but it can still be *expressed* purely with a simple trick, which I'll call "Recipes". Lets define a "Recipe" to be either: 20 | 21 | 1. An array of 3 elements: a string, a list of JSON, and a function that receives a JSON and returns a Recipe. 22 | 23 | 2. An array of 2 elements: the string "return" and a value. 24 | 25 | The cool thing about this format is that it is sufficient to describe any impure algorithm purely! The trick is that, instead of actually printing things or querying databases, a Recipe merely specifies queries, leaving the actual effects to be defined later. This is, for example, the Recipe of the program above: 26 | 27 | ```javascript 28 | const getUserBMIRecipe = 29 | ["prompt", ["What is your height (in meters)?"], (height) => 30 | ["prompt", ["What is your weight (in kg)?"], (weight) => { 31 | var bmi = Number(weight) / (Number(height) * Number(height)); 32 | return ["alert", ["Your BMI is " + bmi], () => 33 | ["return", bmi] 34 | ]; 35 | }] 36 | ]; 37 | ``` 38 | 39 | Note how this is just a pure expression that doesn't do anything by itself. We can, though, convert it into an actual program, by using a suitable definition of `run()`: 40 | 41 | ```javascript 42 | const run = ([request,params,cont]) => () => { 43 | switch (request) { 44 | case "prompt": return Promise.resolve(run(cont(prompt(params[0])))()); 45 | case "alert": return Promise.resolve(run(cont(alert(params[0])))()); 46 | case "return": return Promise.resolve(parmas); 47 | } 48 | }; 49 | ``` 50 | 51 | This would be defined in a library, so you don't need to define it yourself. Lets apply `run()` to `getUserBMIRecipe`: 52 | 53 | ```javascript 54 | const getUserBMI = run(getUserBMIRecipe); 55 | ``` 56 | 57 | The resulting `getUserBMI()` function works exactly like the one we defined previously! You can check it yourself [here](http://jsbin.com/pajujekexa/edit?html,output). Now we can use the same Recipe to generate a mocked version of `getUserBMI`: 58 | 59 | ```javascript 60 | const mock = ([request,params,cont]) => ([input,...rest]) => { 61 | switch (request) { 62 | case "return": return [["returned", params]]; 63 | case "prompt": return [["prompted", params[0]]].concat(mock(cont(input))(rest)); 64 | case "alert": return [["alerted", params[0]]].concat(mock(cont())(rest)); 65 | } 66 | }; 67 | ``` 68 | 69 | `mock()` emulates the behavior of a program based on a list of fake inputs. Let's try it with `1.80` and `70`: 70 | 71 | ```javascript 72 | const mockedGetUserBMI = mock(getUserBMIRecipe); 73 | 74 | console.log(mockedGetUserBMI(["1.80", "70"])); 75 | ``` 76 | 77 | The program above outputs: 78 | 79 | ```javascript 80 | [ [ 'prompted', 'What is your height (in meters)?' ], 81 | [ 'prompted', 'What is your weight (in kg)?' ], 82 | [ 'alerted', 'Your BMI is 21.604938271604937' ], 83 | [ 'returned', 21.604938271604937 ] ] 84 | ``` 85 | 86 | As you can see, this is immensely useful for tests, since it emulates and inspects the execution of a program at any point. A more complete definition of `run()`/`mock()` would allow us to automatically mock any effective program, including those with database accesses, HTTP requests, mouse events and so on. 87 | 88 | There is an obvious problem, though: Recipes are so verbose they even have their own callback hell! That's where [Moon-lang](https://github.com/maiavictor/moon-lang) can be handy. It is an ultra-lightweight language designed to express pure algorithms. Lets define Recipe with Moon: 89 | 90 | ```javascript 91 | const getUserBMIRecipe = Moon.parse(`prompt. alert. return. 92 | height: <(prompt "What is your height (in meters)?") 93 | weight: <(prompt "What is your weight (in kg)?") 94 | bmi: (div (stn weight) (mul (stn height) (stn height))) 95 | (alert (con "Your BMI is " (nts bmi)))> 96 | (return bmi) 97 | `); 98 | ``` 99 | 100 | It avoids the callback hell thanks the `<(...)` and `(...)>` operators, which unlift the result of a side-effective operation. They're, in essence, a generalization of `async/await` for arbitrary effects, allowing Recipe to be expressed succinctly. Note that, since Moon's parser/compiler is fast and generates efficient machine code, you can actually use it instead of JavaScript to define functions. As a bonus, functions defined that way can be easily shared across different programming languages and environments. Of course, that's optional and the technique still applies for JS alone. 101 | 102 | And that is it. In short, recipes are handy tools that allow you to express side-effective algorithms purely by delaying the decision of how to execute those effects, and they can be useful for writting tests and to restrict the effects of certain programs. Here are working examples of this technique: [JavaScript only](example-javascript.js) / [using Moon](example-moon.js). 103 | 104 | --- 105 | 106 | If you're curious, what I'm calling Recipe is actually this algebraic datatype: 107 | 108 | ```haskell 109 | data Recipe a 110 | = Ask String [JSON] (JSON -> Recipe a) 111 | | End a 112 | ``` 113 | 114 | Which is sufficient because it is the monad for JSON-based remote procedure calls between a pure environment and an external oracle. 115 | 116 | The trick to avoid the callback hell used by Moon is a combination of church-encoded [free monads](https://gist.github.com/MaiaVictor/cd979cd66b2494a91eac83c64661a462) with an adaptation of Idris's [bang-notation](https://www.reddit.com/r/haskell/comments/6n9wlc/the_bangnotation_is_surprisingly_powerful/). 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /0001-expressing-side-effects-with-recipes/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0001-implementing-side-effects-with-free-monads", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "buffer-to-arraybuffer": { 7 | "version": "0.0.2", 8 | "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.2.tgz", 9 | "integrity": "sha1-0NgFZNwxhmoZdlFUh7OrYg23yEk=" 10 | }, 11 | "deep-equal": { 12 | "version": "0.2.2", 13 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", 14 | "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" 15 | }, 16 | "defined": { 17 | "version": "0.0.0", 18 | "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", 19 | "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=" 20 | }, 21 | "dom-walk": { 22 | "version": "0.1.1", 23 | "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", 24 | "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" 25 | }, 26 | "for-each": { 27 | "version": "0.3.2", 28 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", 29 | "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=" 30 | }, 31 | "glob": { 32 | "version": "3.2.11", 33 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 34 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=" 35 | }, 36 | "global": { 37 | "version": "4.3.2", 38 | "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", 39 | "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=" 40 | }, 41 | "inherits": { 42 | "version": "2.0.3", 43 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 44 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 45 | }, 46 | "is-function": { 47 | "version": "1.0.1", 48 | "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", 49 | "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" 50 | }, 51 | "lru-cache": { 52 | "version": "2.7.3", 53 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 54 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" 55 | }, 56 | "min-document": { 57 | "version": "2.19.0", 58 | "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", 59 | "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=" 60 | }, 61 | "minimatch": { 62 | "version": "0.3.0", 63 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 64 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=" 65 | }, 66 | "moon-lang": { 67 | "version": "0.1.22", 68 | "resolved": "https://registry.npmjs.org/moon-lang/-/moon-lang-0.1.22.tgz", 69 | "integrity": "sha512-2gHuSh/HgzYR95Zt3qs4DqHXlMQYJDROpnx191lnIOSR6IbwXyz28Tqj+4qHZt4yLXgWV+RVMDeoc5OJeIYy5A==" 70 | }, 71 | "object-assign": { 72 | "version": "3.0.0", 73 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", 74 | "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" 75 | }, 76 | "object-inspect": { 77 | "version": "0.4.0", 78 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz", 79 | "integrity": "sha1-9RV8EWwUVbJDsG7pdwM5LFrYn+w=" 80 | }, 81 | "once": { 82 | "version": "1.4.0", 83 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 84 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" 85 | }, 86 | "parse-headers": { 87 | "version": "2.0.1", 88 | "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", 89 | "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=" 90 | }, 91 | "process": { 92 | "version": "0.5.2", 93 | "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", 94 | "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" 95 | }, 96 | "query-string": { 97 | "version": "2.4.2", 98 | "resolved": "https://registry.npmjs.org/query-string/-/query-string-2.4.2.tgz", 99 | "integrity": "sha1-fbBmZCCAS6qSrp8miWKFWnYUPfs=" 100 | }, 101 | "resumer": { 102 | "version": "0.0.0", 103 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 104 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=" 105 | }, 106 | "sigmund": { 107 | "version": "1.0.1", 108 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 109 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" 110 | }, 111 | "simple-get": { 112 | "version": "1.4.3", 113 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz", 114 | "integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=" 115 | }, 116 | "strict-uri-encode": { 117 | "version": "1.1.0", 118 | "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", 119 | "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" 120 | }, 121 | "tape": { 122 | "version": "3.6.1", 123 | "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz", 124 | "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=" 125 | }, 126 | "through": { 127 | "version": "2.3.8", 128 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 129 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 130 | }, 131 | "timed-out": { 132 | "version": "2.0.0", 133 | "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", 134 | "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=" 135 | }, 136 | "trim": { 137 | "version": "0.0.1", 138 | "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", 139 | "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" 140 | }, 141 | "unzip-response": { 142 | "version": "1.0.2", 143 | "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", 144 | "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" 145 | }, 146 | "url-set-query": { 147 | "version": "1.0.0", 148 | "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", 149 | "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=" 150 | }, 151 | "wrappy": { 152 | "version": "1.0.2", 153 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 154 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 155 | }, 156 | "xhr": { 157 | "version": "2.4.0", 158 | "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", 159 | "integrity": "sha1-4W5mpF+GmGHu76tBbV7/ci3ECZM=" 160 | }, 161 | "xhr-request": { 162 | "version": "1.0.1", 163 | "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.0.1.tgz", 164 | "integrity": "sha1-g/CKSyC+7Geowcco6BAvTJ7svdo=" 165 | }, 166 | "xhr-request-promise": { 167 | "version": "0.1.2", 168 | "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz", 169 | "integrity": "sha1-NDxE0e53JrhkgGloLQ+EDIO0Jh0=" 170 | }, 171 | "xtend": { 172 | "version": "4.0.1", 173 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 174 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /0004-supercompilation-for-free/README.md: -------------------------------------------------------------------------------- 1 | # Supercompilation-like performance on the abstract algorithm 2 | 3 | The [abstract-algorithm](https://github.com/MaiaVictor/abstract-algorithm) is how I call a very simple (~200 LOC!), elegant abstract machine that evaluates λ-calculus terms optimally. In this article, I'll show 2 small experiments in which my 200-LOC JavaScript evaluator beats GHC (The Glorious Glasgow Haskell Compiler) by a few orders of magnitude. 4 | 5 | ## zipWith tuples 6 | 7 | Mind the following program: 8 | 9 | ```haskell 10 | apply_n_times n f x = go n x where { go 0 r = r; go n r = go (n - 1) (f r) } 11 | xor b = case b of { True -> not; False -> id } 12 | toList (x,y,z,w) = [x,y,z,w] 13 | fromList [x,y,z,w] = (x,y,z,w) 14 | 15 | zip4 :: (a -> b -> c) -> (a,a,a,a) -> (b,b,b,b) -> (c,c,c,c) 16 | zip4 f a b = fromList (zipWith f (toList a) (toList b)) 17 | 18 | main = do 19 | let a = (True, True, False, False) 20 | let b = (True, False, True, False) 21 | print $ apply_n_times (2 ^ 25) (zip4 xor a) b 22 | ``` 23 | 24 | It takes the pairwise xor of two quadruples 33 million times, taking `9` seconds to execute on my computer (compiled with `ghc --O2`). It is also stupid: `zip4 xor a (zip4 xor a (zip4 xor a ... b))` is always equal to `b` (for even applications). One may wonder: could a sufficiently smart compiler optimize it? Perhaps, but, before anything, it must be able to remove all abstractions used in `zip4`. Sadly, `zipWith` is known to scape Haskell's [short-cut fusion](https://wiki.haskell.org/Correctness_of_short_cut_fusion) for lists. Let's help the compiler by manually deforesting it: 25 | 26 | ```haskell 27 | zip4 :: (a -> b -> c) -> (a,a,a,a) -> (b,b,b,b) -> (c,c,c,c) 28 | zip4 f (ax,ay,az,aw) (bx,by,bz,bw) = (f ax bx, f ay by, f az bz, f aw bw) 29 | ``` 30 | 31 | Can GHC optimize it now? Well, no: this still takes `8.6` seconds on my computer. As you can see, the problem here is not deforestation, but the sheer amount of function calls. We could improve it by reasoning about the runtime execution; but that's a complex technique, and the very premise behind the field of [supercompilation](https://ghc.haskell.org/trac/ghc/wiki/Supercompilation). 32 | 33 | But what about the [abstract algorithm](https://github.com/MaiaVictor/abstract-algorithm)? One of its most interesting aspects is sometimes running programs that "clearly shouldn't execute this fast". The first time I noticed this effect was in 2015, when the abstract algorithm was capable of computing the modulus of `(200^200)%13` encoded as a unary number. Such computation wouldn't fit a memory the size of the universe, yet the abstract algorithm had no trouble completing it. You can read about this on [Stack Overflow](https://stackoverflow.com/questions/31707614/why-are-%CE%BB-calculus-optimal-evaluators-able-to-compute-big-modular-exponentiation). This leads us to wonder how far can that algorithm go. What about `zip4`? Can it optimize it? Indeed. Mind the following λ-program: 34 | 35 | ```haskell 36 | zip4 = λf. λa. λb. λtuple. 37 | (a λax. λay. λbz. λbw. 38 | (b λbx. λby. λbz. λbw. 39 | tuple (f ax bx) (f ay by) (f az bz) (f aw bw))) 40 | 41 | main = 42 | let a = λtuple. tuple True True False False in 43 | let b = λtuple. tuple True False True False in 44 | apply_n_times (pow 2 25) (zip4 xor a) b 45 | ``` 46 | 47 | It is the exact equivalent of the second Haskell example, yet the abstract algorithm takes only `0.058` seconds to evaluate it. That is a 148x speedup against a 26-years-old state-of-art compiler, which isn't bad for a 200-LOC JavaScript compiler! But what about the first definition? 48 | 49 | ```haskell 50 | zip4 = λf. λa. λb. 51 | fromList (zipWith f (toList a) (toList b)) 52 | ``` 53 | 54 | This one is trickier. If you evaluate it as is, it will be exponential, too. But I've recently discovered an amazing technique that allows the definition to stay elegant and fast: 55 | 56 | ```haskell 57 | zip4 = λf. λa. λb. λt. 58 | open a λa. 59 | open b λb. 60 | fromList (zipWith f (toList a) (toList b)) t 61 | ``` 62 | 63 | Here, `open` is a function that takes a 4-tuple (`T`), applies it to 4 newly-created variables (`λa. λb. λc. λd. T a b c d`), and then calls a continuation with that value. In other words, you just eta-expand it, describing the shape of an input variable; that is all it takes for the algorithm to fully "deforest" the definitions of `fromList`, `zipWith` and `toList`, no complex fusion strategy was needed! But what is truly remarkable about this technique is that it works for any function, no matter how high-level or recursive it is: as long you "open" all inputs, the optimal algorithm will be able to remove every single layer of abstraction it has, recovering an ultra-fast, low-level definition from your high-level specification; which is exactly what a supercompiler does! This allowed me to, for example, define a generic `zip` for `n-tuples` which is still optimal. Generic programming on λ-encoded data might be interesting. 64 | 65 | ## Brazillions of successors 66 | 67 | Of course, `apply_n_times n (zip4 xor A)` is stupid; it does nothing. Mind the following, slightly more useful Haskell program: 68 | 69 | ```haskell 70 | data Bin = O Bin | I Bin 71 | 72 | succ :: Bin -> Bin 73 | succ (O xs) = I xs 74 | succ (I xs) = O (succ xs) 75 | 76 | zero :: Bin 77 | zero = O zero 78 | 79 | main = putStrLn $ peek 64 (apply_n_times (2 ^ 63) succ zero) 80 | ``` 81 | 82 | Here, `succ` takes the successor of a binary string, so, `suc(0000) = 1000`, `suc(1000) = 0100`, `suc(0100) = 1100` and so on; nothing unusual. Now, printing the result of `apply_n_times (2 ^ 63) succ zero` must be obviously impossible, right? That'd require `9,223,372,036,854,775,808` calls to `succ`, which I estimate would take about `69,730` years on my computer (compiled with `ghc -O2`). But what if we implement the same algorithm in the λ-calculus? Can the abstract algorithm magically optimize it? Why not. The direct translation looks like: 83 | 84 | ```haskell 85 | zero = /O zero 86 | succ = λxs. λO. λI. xs I λxs. O (succ xs) 87 | main = peek 64 (apply_n_times (pow 2 63) succ zero) 88 | ``` 89 | 90 | When executed, it correctly prints the result in `0.08` seconds. That is a `27,487,790,694,400x` speedup, which may be noticeable. Interestingly, but not surprisingly, changing the base makes the computation more expensive. Computing the first 8 bits of `3^255` takes 1 second, or `2,597,966` graph rewrites; which is fairly decent, given that doing the same in other languages would need at least 91 | 92 | ``` 93 | 46,336,150,792,381,577,588,313,262,263,220,434,371,406,283,602,843,045,997,201,608,143,345,357,543,255,478,647,000,589,718,036,536,507,270,555,180,182,966,478,507 94 | ``` 95 | 96 | function calls. 97 | 98 | ## DIY 99 | 100 | To replicate those experiments on your computer, install the `abstract-algorithm` module from npm: 101 | 102 | ```bash 103 | npm i -g abstract-algorithm 104 | ``` 105 | 106 | And run `absal` on the `.lam` programs of this repository: 107 | 108 | ```bash 109 | git clone https://github.com/maiavictor/articles 110 | cd 0004-supercompilation-for-free 111 | 112 | ghc -O2 test_a.hs -o test_a 113 | time ./test_a 114 | 115 | ghc -O2 test_b.hs -o test_b 116 | time ./test_b 117 | 118 | ghc -O2 test_c.hs -o test_c 119 | time ./test_c 120 | 121 | absal --stats --bruijn test_a 122 | absal --stats --bruijn test_b 123 | absal --stats --bruijn test_c 124 | ``` 125 | 126 | Alternatively, install Formality with: 127 | 128 | ```bash 129 | npm i -g formality 130 | ``` 131 | 132 | And run: 133 | 134 | ``` 135 | fm -o test_a 136 | ``` 137 | 138 | Note those programs have a slightly uglier syntax than what I presented, as I wanted to have a very simple parser. Outputs are obviously λ-encoded, so you may need to understand those in order to interpret them. 139 | -------------------------------------------------------------------------------- /0003-theorem-proving-vs-testing/README.md: -------------------------------------------------------------------------------- 1 | # Theorem proving VS testing VS auditing 2 | 3 | On this article, I'll explain how theorem proving can be used as an alternative to testing and human auditing on the task of writing correct code. 4 | 5 | ## Implementing the code 6 | 7 | When trying to find bugs in functions, the most widely adopted methods are auditing, and writing tests. For example, suppose that we wrote an "adder" function, which computes the addition of two bitstrings with a carry bit: 8 | 9 | ```haskell 10 | data Bit = O | I deriving Show 11 | type Bits = [Bit] 12 | 13 | adder :: Bit -> Bits -> Bits -> Bits 14 | adder O [] [] = [] 15 | adder O [] (O : ys) = O : adder O [] ys 16 | adder O [] (I : ys) = I : adder O [] ys 17 | adder O (O : xs) [] = O : adder O xs [] 18 | adder O (O : xs) (O : ys) = O : adder O xs ys 19 | adder O (O : xs) (I : ys) = I : adder O xs ys 20 | adder O (I : xs) [] = I : adder O xs [] 21 | adder O (I : xs) (O : ys) = I : adder O xs ys 22 | adder O (I : xs) (I : ys) = O : adder I xs ys 23 | adder I [] [] = I : [] 24 | adder I [] (O : ys) = I : adder O [] ys 25 | adder I [] (I : ys) = O : adder I [] ys 26 | adder I (O : xs) [] = I : adder O xs [] 27 | adder I (O : xs) (O : ys) = I : adder O xs ys 28 | adder I (O : xs) (I : ys) = O : adder I xs ys 29 | adder I (I : xs) [] = O : adder I xs [] 30 | adder I (I : xs) (O : ys) = I : adder O xs ys 31 | adder I (I : xs) (I : ys) = I : adder I xs ys 32 | ``` 33 | 34 | ## Auditing the code 35 | 36 | One way to check if that function is correct is to audit it. That is a cool way to say we examine it carefully until we are convinced it is correct. Some do it profissionally, so, hiring them might be a good idea. I've looked it for some time and, while I'm a little bit sleepy, I didn't notice anything suspect, so I'm proud to claim the audit a success. Let's move on. 37 | 38 | ## Testing the code 39 | 40 | Another way to improve our confidence is by actually testing the function. That means we run it and check if it returns what we expect. For example: 41 | 42 | ```haskell 43 | main :: IO () 44 | main = print (adder [I,O,O,I] [O,I,I,O]) // prints [I,I,I,I]! 45 | ``` 46 | 47 | This program outputs `1111`, which is the sum of `1001 + 0110`, as expected. Great, but that isn't enough. To improve our rigor, we could create a `test` function, which compares our adder to the result of the native integer addition function: 48 | 49 | ```haskell 50 | test :: Integer -> Integer -> Bool 51 | test a b = to (adder O (from a) (from b)) == a + b where 52 | 53 | from :: Integer -> Bits 54 | from = go where 55 | go 0 = [] 56 | go n | mod n 2 == 0 = O : go (div n 2) 57 | | otherwise = I : go (div n 2) 58 | 59 | to :: Bits -> Integer 60 | to = go 1 where 61 | go :: Integer -> Bits -> Integer 62 | go k []  = 0 63 | go k (O : xs) = go (k * 2) xs 64 | go k (I : xs) = k + go (k * 2) xs 65 | ``` 66 | 67 | We could, then, use it to write a lot of unit tests: 68 | 69 | ```haskell 70 | main :: IO () 71 | main = do 72 | print $ test 3 7 73 | print $ test 1 5 74 | print $ test 8 4 75 | print $ test 6 9 76 | print $ test 5 5 77 | print $ test 7 4 78 | print $ test 3 2 79 | print $ test 9 12 80 | print $ test 32 64 81 | print $ test 99 42 82 | print $ test 69 69 83 | print $ test 100 200 84 | print $ test 321 456 85 | print $ test 200 1239 86 | print $ test 256 1024 87 | print $ test 4096 7654 88 | print $ test 98765 10546 89 | ``` 90 | 91 | All the tests pass, so, I declare my tests as a success. Let's move on. 92 | 93 | ## Proving the code correct 94 | 95 | Let's try something different now: instead of testing, let's try to **prove** our function correct. Can we? First, let's translate it to Agda: 96 | 97 | ```haskell 98 | adder : Bit -> Bits -> Bits -> Bits 99 | adder O [] [] = [] 100 | adder O [] (O ∷ ys) = O ∷ adder O [] ys 101 | adder O [] (I ∷ ys) = I ∷ adder O [] ys 102 | adder O (O ∷ xs) [] = O ∷ adder O xs [] 103 | adder O (O ∷ xs) (O ∷ ys) = O ∷ adder O xs ys 104 | adder O (O ∷ xs) (I ∷ ys) = I ∷ adder O xs ys 105 | adder O (I ∷ xs) [] = I ∷ adder O xs [] 106 | adder O (I ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 107 | adder O (I ∷ xs) (I ∷ ys) = O ∷ adder I xs ys 108 | adder I [] [] = I ∷ [] 109 | adder I [] (O ∷ ys) = I ∷ adder O [] ys 110 | adder I [] (I ∷ ys) = O ∷ adder I [] ys 111 | adder I (O ∷ xs) [] = I ∷ adder O xs [] 112 | adder I (O ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 113 | adder I (O ∷ xs) (I ∷ ys) = O ∷ adder I xs ys 114 | adder I (I ∷ xs) [] = O ∷ adder I xs [] 115 | adder I (I ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 116 | adder I (I ∷ xs) (I ∷ ys) = I ∷ adder I xs ys 117 | ``` 118 | 119 | Now, let's arbitrarily try to prove that `adder` commutes, as it should. For that, we must write the following function: 120 | 121 | 122 | ```haskell 123 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 124 | ``` 125 | 126 | This is a function that, for every bit `x`, and for all bitstrings `a` and `b`, returns a term of type `adder x a b ≡ adder x b a`. Writing a function of that type is the same as proving `adder` commutes, for reasons out of scope. But wtf is that type? Well, it is a type that, given the rules of Agda, can only be instantiated if the two sides of the equation are actually equal. What? Whatever, let's start our implementation by writing all the cases: 127 | 128 | ```haskell 129 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 130 | adder-comm O [] [] = ? 131 | adder-comm O [] (O ∷ b) = ? 132 | adder-comm O [] (I ∷ b) = ? 133 | adder-comm O (O ∷ a) [] = ? 134 | adder-comm O (I ∷ a) [] = ? 135 | adder-comm O (O ∷ a) (O ∷ b) = ? 136 | adder-comm O (O ∷ a) (I ∷ b) = ? 137 | adder-comm O (I ∷ a) (O ∷ b) = ? 138 | adder-comm O (I ∷ a) (I ∷ b) = ? 139 | adder-comm I [] [] = ? 140 | adder-comm I [] (O ∷ b) = ? 141 | adder-comm I [] (I ∷ b) = ? 142 | adder-comm I (O ∷ a) [] = ? 143 | adder-comm I (I ∷ a) [] = ? 144 | adder-comm I (O ∷ a) (O ∷ b) = ? 145 | adder-comm I (O ∷ a) (I ∷ b) = ? 146 | adder-comm I (I ∷ a) (O ∷ b) = ? 147 | adder-comm I (I ∷ a) (I ∷ b) = ? 148 | ``` 149 | 150 | We've been implementing functions for our entire lives. Implementing this one can't be that hard, can it? In fact, it is almost complete. The only new thing here is returning a term of type `adder x a b ≡ adder x b a`. But how? 151 | 152 | One of the cool things about Agda is that it allows us to ask the type of a hole. If we, thus, ask for the type of any of those `?`s, we should see `adder x a b ≡ adder x b a`, right? After all, that's what I just said we are trying to return! Right? Well, no. If we ask the type of the first one, this is what we see: 153 | 154 | ```haskell 155 | [] ≡ [] 156 | ``` 157 | 158 | But why? That's different from the return type of the function. Because Agda substituded `x`, `a` and `b` by `O`, `[]` and `[]`, which are the concrete values of those variables on the first case. This causes the type to reduce from: 159 | 160 | ```haskell 161 | adder x a b ≡ adder x a b 162 | ``` 163 | 164 | To: 165 | 166 | ```haskell 167 | adder O [] [] ≡ adder O [] [] 168 | ``` 169 | 170 | To: 171 | 172 | ```haskell 173 | [] ≡ [] 174 | ``` 175 | 176 | Which is simpler. But, again, how the hell do we instantiate a term of type `[] ≡ []`? Well, Agda has this cool function called `refl` which, for any term `x`, returns a term of type `x ≡ x`. Or, in other words: *"any term is equal to itself"*. Can we, thus, just use `refl` there? Yes, we can: 177 | 178 | ```haskell 179 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 180 | adder-comm O [] [] = refl {x = []} 181 | adder-comm O [] (O ∷ b) = ? 182 | adder-comm O [] (I ∷ b) = ? 183 | adder-comm O (O ∷ a) [] = ? 184 | adder-comm O (I ∷ a) [] = ? 185 | adder-comm O (O ∷ a) (O ∷ b) = ? 186 | adder-comm O (O ∷ a) (I ∷ b) = ? 187 | adder-comm O (I ∷ a) (O ∷ b) = ? 188 | adder-comm O (I ∷ a) (I ∷ b) = ? 189 | adder-comm I [] [] = ? 190 | adder-comm I [] (O ∷ b) = ? 191 | adder-comm I [] (I ∷ b) = ? 192 | adder-comm I (O ∷ a) [] = ? 193 | adder-comm I (I ∷ a) [] = ? 194 | adder-comm I (O ∷ a) (O ∷ b) = ? 195 | adder-comm I (O ∷ a) (I ∷ b) = ? 196 | adder-comm I (I ∷ a) (O ∷ b) = ? 197 | adder-comm I (I ∷ a) (I ∷ b) = ? 198 | ``` 199 | 200 | What? OK, if that didn't make any sense to you, don't worry, it is hard to grasp `refl` on the first try. Not because it is complex, it is just different. The point is, I managed to implement the first case, because: 201 | 202 | 1. Agda allowed me to, on that case, return a term of type `[] ≡ []`, instead of `adder x a b ≡ adder x b a`, because it substituted the variables by the concrete values of that case. 203 | 204 | 2. Agda has a magic function called `refl` which, for any term `x`, returns a term of type `x ≡ x`. 205 | 206 | 3. I called `refl {x = []}`, which gave me a term of type `[] ≡ []`. 207 | 208 | 4. I returned it, because that is what I needed to return! 209 | 210 | In English-math terms, what I did is the equivalent of saying: *"if `x` is `O`, and `a` and `b` are empty, then, `adder x a b` reduces to `[]`, and `adder x b a` also reduces to `[]`... those are syntactically equal (`refl`), thus, for that case, `adder x a b ≡ adder x b a`"*. 211 | 212 | Ok, let's move on and check the type of the second hole. 213 | 214 | ```haskell 215 | O ∷ adder O [] b ≡ O ∷ adder O b [] 216 | ``` 217 | 218 | This time, Agda substituted the variables `x`, `a` and `b` by `O`, `[]` and `(O :: b)`, again their concrete values on that case. But, now, it didn't reduce to a type that is obviously equal. If we write: 219 | 220 | ``` 221 | refl {x = O ∷ adder O [] b} 222 | ``` 223 | 224 | We'll have a term of type: 225 | 226 | ``` 227 | O ∷ adder O [] b ≡ O ∷ adder O [] b 228 | ``` 229 | 230 | Which is not what we need to return: notice the position of `b`. The so magical "refl" won't help us this time. What now? Again, we need to build a term of type `O ∷ adder O [] b ≡ O ∷ adder O b []`. Let's try the following: 231 | 232 | ```haskell 233 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 234 | adder-comm O [] [] = refl {x = []} 235 | adder-comm O [] (O ∷ b) = let rec = adder-comm O [] b in ? 236 | adder-comm O [] (I ∷ b) = ? 237 | adder-comm O (O ∷ a) [] = ? 238 | adder-comm O (I ∷ a) [] = ? 239 | adder-comm O (O ∷ a) (O ∷ b) = ? 240 | adder-comm O (O ∷ a) (I ∷ b) = ? 241 | adder-comm O (I ∷ a) (O ∷ b) = ? 242 | adder-comm O (I ∷ a) (I ∷ b) = ? 243 | adder-comm I [] [] = ? 244 | adder-comm I [] (O ∷ b) = ? 245 | adder-comm I [] (I ∷ b) = ? 246 | adder-comm I (O ∷ a) [] = ? 247 | adder-comm I (I ∷ a) [] = ? 248 | adder-comm I (O ∷ a) (O ∷ b) = ? 249 | adder-comm I (O ∷ a) (I ∷ b) = ? 250 | adder-comm I (I ∷ a) (O ∷ b) = ? 251 | adder-comm I (I ∷ a) (I ∷ b) = ? 252 | ``` 253 | 254 | So, what did happen? We just called `adder-comm` recursively with the arguments `O`, `[]` and `(O :: b)`. That returned a term, `rec`, of type: 255 | 256 | ```haskell 257 | rec : adder O [] b ≡ adder O b [] 258 | ``` 259 | 260 | How? Well, notice the function `adder-comm` (which we are defining) returns a term of type `adder x a b ≡ adder x b a`, so we used it on `O`, `[]` and `b` to get a term of type `adder O [] b ≡ adder O b []`. Can we do that? Yes, we can, in the same way that we can call functions recursively in Haskell. That's called the induction hypothesis. 261 | 262 | Now we have a term, `rec`, with a type that is surprisingly close to what we need to return: 263 | 264 | 265 | ```haskell 266 | O ∷ adder O [] b ≡ O ∷ adder O b [] 267 | ``` 268 | 269 | The only difference is that there is an extra `O ∷` on each side. What now? Well, Agda has another nice function called `cong`, with the following type: 270 | 271 | ```haskell 272 | cong : {x y : A} → (f : A → B) → x ≡ y → f x ≡ f y 273 | ``` 274 | 275 | Or, in other words, it allows us to take a term of type `x ≡ y`, and return a term of type `f x ≡ f y`, for any `f`. If we apply `λ x -> O :: x` to both sides of the type of `rec`, then we get a term of type: 276 | 277 | ```haskell 278 | O ∷ adder O [] b ≡ O ∷ adder O b [] 279 | ``` 280 | 281 | Which is exactly the type we needed! We can, thus, complete that case by applying `cong` to `rec`. In fact, we can apply the same technique and keep filling all the cases below it: 282 | 283 | ```haskell 284 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 285 | adder-comm O [] [] = refl {x = []} 286 | adder-comm O [] (O ∷ b) = cong (λ x → O ∷ x) (adder-comm O [] b) 287 | adder-comm O [] (I ∷ b) = cong (λ x → I ∷ x) (adder-comm O [] b) 288 | adder-comm O (O ∷ a) [] = cong (λ x → O ∷ x) (adder-comm O a []) 289 | adder-comm O (I ∷ a) [] = cong (λ x → I ∷ x) (adder-comm O a []) 290 | adder-comm O (O ∷ a) (O ∷ b) = cong (λ x → O ∷ x) (adder-comm O a b) 291 | adder-comm O (O ∷ a) (I ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 292 | adder-comm O (I ∷ a) (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 293 | adder-comm O (I ∷ a) (I ∷ b) = cong (λ x → O ∷ x) (adder-comm I a b) 294 | adder-comm I [] [] = refl {x = []} 295 | adder-comm I [] (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O [] b) 296 | adder-comm I [] (I ∷ b) = cong (λ x → O ∷ x) (adder-comm I [] b) 297 | adder-comm I (O ∷ a) [] = cong (λ x → I ∷ x) (adder-comm O a []) 298 | adder-comm I (I ∷ a) [] = cong (λ x → O ∷ x) (adder-comm I a []) 299 | adder-comm I (O ∷ a) (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 300 | adder-comm I (O ∷ a) (I ∷ b) = ? 301 | adder-comm I (I ∷ a) (O ∷ b) = ? 302 | adder-comm I (I ∷ a) (I ∷ b) = ? 303 | ``` 304 | 305 | Cool, isn't it? But there is a problem with the 3rd-last case. If we ask for the type of its hole, we get: 306 | 307 | ```haskell 308 | O ∷ adder I a b ≡ I ∷ adder O b a 309 | ``` 310 | 311 | Here, the induction hypothesis won't help. Even if we could call `adder-comm` recursively to get a term of type `adder I a b ≡ adder O b a`, there would be no way to turn it into `O ∷ adder I a b ≡ I ∷ adder O b a`. The problem is that each side has a list which starts with a different bit. So, what can we do to complete that clause? Nothing! There is no function in Agda that helps us here, because, guess what? Our function is **not** commutative. We've found a bug! How could that be? Let's check the corresponding clauses: 312 | 313 | ```haskell 314 | adder I (O ∷ xs) (I ∷ ys) = O ∷ adder I xs ys 315 | adder I (I ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 316 | ``` 317 | 318 | There is our mistake: on the `1 + 1 + 0` case, we return `1`, with a carry bit `0`, whereas we should return `0`, with a carry bit `1`! Let's fix it: 319 | 320 | ```haskell 321 | adder : Bit -> Bits -> Bits -> Bits 322 | adder O [] [] = [] 323 | adder O [] (O ∷ ys) = O ∷ adder O [] ys 324 | adder O [] (I ∷ ys) = I ∷ adder O [] ys 325 | adder O (O ∷ xs) [] = O ∷ adder O xs [] 326 | adder O (O ∷ xs) (O ∷ ys) = O ∷ adder O xs ys 327 | adder O (O ∷ xs) (I ∷ ys) = I ∷ adder O xs ys 328 | adder O (I ∷ xs) [] = I ∷ adder O xs [] 329 | adder O (I ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 330 | adder O (I ∷ xs) (I ∷ ys) = O ∷ adder I xs ys 331 | adder I [] [] = I ∷ [] 332 | adder I [] (O ∷ ys) = I ∷ adder O [] ys 333 | adder I [] (I ∷ ys) = O ∷ adder I [] ys 334 | adder I (O ∷ xs) [] = I ∷ adder O xs [] 335 | adder I (O ∷ xs) (O ∷ ys) = I ∷ adder O xs ys 336 | adder I (O ∷ xs) (I ∷ ys) = O ∷ adder I xs ys 337 | adder I (I ∷ xs) [] = O ∷ adder I xs [] 338 | adder I (I ∷ xs) (O ∷ ys) = O ∷ adder I xs ys 339 | adder I (I ∷ xs) (I ∷ ys) = I ∷ adder I xs ys 340 | ``` 341 | 342 | Can we complete `adder-comm` now? Sure: 343 | 344 | ```haskell 345 | adder-comm : forall x a b -> adder x a b ≡ adder x b a 346 | adder-comm O [] [] = refl {x = []} 347 | adder-comm O [] (O ∷ b) = cong (λ x → O ∷ x) (adder-comm O [] b) 348 | adder-comm O [] (I ∷ b) = cong (λ x → I ∷ x) (adder-comm O [] b) 349 | adder-comm O (O ∷ a) [] = cong (λ x → O ∷ x) (adder-comm O a []) 350 | adder-comm O (I ∷ a) [] = cong (λ x → I ∷ x) (adder-comm O a []) 351 | adder-comm O (O ∷ a) (O ∷ b) = cong (λ x → O ∷ x) (adder-comm O a b) 352 | adder-comm O (O ∷ a) (I ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 353 | adder-comm O (I ∷ a) (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 354 | adder-comm O (I ∷ a) (I ∷ b) = cong (λ x → O ∷ x) (adder-comm I a b) 355 | adder-comm I [] [] = refl {x = []} 356 | adder-comm I [] (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O [] b) 357 | adder-comm I [] (I ∷ b) = cong (λ x → O ∷ x) (adder-comm I [] b) 358 | adder-comm I (O ∷ a) [] = cong (λ x → I ∷ x) (adder-comm O a []) 359 | adder-comm I (I ∷ a) [] = cong (λ x → O ∷ x) (adder-comm I a []) 360 | adder-comm I (O ∷ a) (O ∷ b) = cong (λ x → I ∷ x) (adder-comm O a b) 361 | adder-comm I (O ∷ a) (I ∷ b) = cong (λ x → O ∷ x) (adder-comm I a b) 362 | adder-comm I (I ∷ a) (O ∷ b) = cong (λ x → O ∷ x) (adder-comm I a b) 363 | adder-comm I (I ∷ a) (I ∷ b) = cong (λ x → I ∷ x) (adder-comm I a b) 364 | ``` 365 | 366 | If we compile the program above, we get no type error, which means we successfully proved `adder` commutes! Yay! 367 | 368 | 369 | ## Conclusion 370 | 371 | By trying to prove a property we expected to hold on `adder`, the compiler naturally guided us to finding a bug; a bug that passed unnoticed through all tests! Did we prove the function correct? No, because... *"define what correct means"*! But we've proven a property which we expect from any correct implementation of `add`. If we wanted a stronger proof, we could implement this function: 372 | 373 | ```haskell 374 | adder-adds : forall a b -> to (adder O (from a) (from b)) = a + b 375 | ``` 376 | 377 | Which is equivalent to the tests we did comparing `adder` to the built-in `add` function, except this time it'd be a proof rather than just tests. We could, similarly, prove any arbitrary specification of what we consider to be a correct implementation of "add". 378 | 379 | ## Relevance to smart-contracts 380 | 381 | Now, you might think: "if we wrote more tests, we'd have spotted that one too!" True, but that's a pretty simple function. Now, think of something much more complex, like: 382 | 383 | ```haskell 384 | contract-is-undrainable : forall txs -> balance (EVM-apply txs (EMV-deploy bytecode)) > 0 385 | contract-is-undrainable = ... 386 | ``` 387 | 388 | The type of that function says that, no matter what transactions certain contract receives, its balance will never fall to zero. That is a nice property to have in a contract that must be undrainable. You could write as many tests as you want, but you'd never be 100% certain your contract ins undrainable. A proof gives 100% certainty. 389 | 390 | That is, IMO, how we will begin trusting smart-contracts that deal with a ton of money. Rather than doing several auditions and writing lots of tests (like we did with TheDAO), we'll, instead, ask for the developers to prove that the contract satisfies certain expectations, which are expressed as specifications (types) that are much easier to read than the code of the contract itself. 391 | 392 | --- 393 | 394 | (edit) 395 | 396 | This is, by the way, a much simpler implementation: 397 | 398 | ```agda 399 | inc : {n : Nat} -> Bits n -> Bits n 400 | inc [] = [] 401 | inc (O ∷ xs) = I ∷ xs 402 | inc (I ∷ xs) = O ∷ inc xs 403 | 404 | add : {n : Nat} -> Bits n -> Bits n -> Bits n 405 | add []  [] = [] 406 | add (O ∷ xs) (O ∷ ys) = O ∷ (add xs ys) 407 | add (O ∷ xs) (I ∷ ys) = I ∷ (add xs ys) 408 | add (I ∷ xs) (O ∷ ys) = I ∷ (add xs ys) 409 | add (I ∷ xs) (I ∷ ys) = inc (I ∷ (add xs ys)) 410 | 411 | add-comm : {n : Nat} -> (a : Bits n) -> (b : Bits n) -> add a b ≡ add b a 412 | add-comm [] [] = refl 413 | add-comm (O ∷ xs) (O ∷ ys) = cong O∷ (add-comm xs ys) 414 | add-comm (O ∷ xs) (I ∷ ys) = cong I∷ (add-comm xs ys) 415 | add-comm (I ∷ xs) (O ∷ ys) = cong I∷ (add-comm xs ys) 416 | add-comm (I ∷ xs) (I ∷ ys) = cong O∷ (cong inc (add-comm xs ys)) 417 | ``` 418 | 419 | Guess my skills are slighly above they were before on this seemingly infinite stairway. 420 | --------------------------------------------------------------------------------