├── .gitignore └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Y combinator: A very short explanation 2 | === 3 | 4 | The following is the most distilled top-down explanation of the Y combinator I 5 | could possibly come up with, that - according to my hopes - still remains 6 | comprehensible. You might want to read this either as a warm-up before diving 7 | deeper, or after reading 8 | [one](http://blog.tomtung.com/2012/10/yet-another-y-combinator-tutorial/) or 9 | [two](https://www.cs.toronto.edu/~david/courses/csc324_w15/extra/ycomb.html) 10 | more verbose tutorials with a bottom-up approach, if the concept hasn't quite 11 | clicked yet. 12 | 13 | Caveat: being short does not mean that you can read faster than usual - in fact, 14 | exactly the opposite. The text is very dense, and every sentence is packed with 15 | a lot of information: be sure to take your time. 16 | 17 | The code itself is in [Clojure](https://clojure.org/), a popular modern Lisp 18 | dialect - the only thing that should not be instantly comprehensible for Lispers 19 | is the following built-in macro: `#(... %)` is an alternative syntax for 20 | denoting a lambda, the same as `(fn [x] (... x))` - Clojure's equivalent of 21 | `(lambda (x) (... x))` -, with `%` representing the place of the first or only 22 | function argument in the body. I will use that form to denote those lambdas 23 | that are really just redundant wrappers, serving no other purpose than to delay 24 | the evaluation of their wrapped expression. 25 | 26 | Problem 27 | --- 28 | We have an anonymous function `f` that we'd like to be able to call recursively, 29 | without the means of explicit self-reference. 30 | 31 | ```clojure 32 | (def f (fn [x] ( 33 | ;; point of recursion somewhere in body 34 | ))) 35 | ``` 36 | 37 | Solution 38 | --- 39 | The **Y combinator** is a clever function that can create a modified version of 40 | `f` that is able to recurse without knowing its own name. I will first show the 41 | so-called strict version of the Y combinator, commonly called the **Z 42 | combinator** - the subtle difference will be addressed later. 43 | 44 | As a preliminary step, we need to move the self-reference out from the function 45 | `f`. That is, the Z combinator does not work on the original function, but 46 | expects a wrapped version of `f` (a maker for `f`, if you like), that returns an 47 | `f` that calls the maker's _argument_ - a bound variable in the enclosing scope, 48 | that's perfectly OK - at the point of recursion. 49 | 50 | ```clojure 51 | (def f-maker (fn [f-self] 52 | ;; Spoiler: the function returned here will be a clever, 53 | ;; "self-replicating" version of our original `f`, if 54 | ;; provided with the right form of `f-self` by the maker. 55 | (fn [x] ( 56 | ;; call the provided `f-self` at the point of recursion 57 | )))) 58 | ``` 59 | 60 | Now the trick waiting to be performed by us is passing this new kind of `f` 61 | _itself_ as argument to `f-maker` somehow. On how that can be achieved in an 62 | indefinite recursive situation, without manually feeding `f-maker` with an `f` 63 | that is created by an `f-maker` taking an `f` that is created by... and so on, 64 | that is, writing out an unfinishable chain of nested calls, see the rest. 65 | 66 | Without further ado, the magic function: 67 | 68 | ```clojure 69 | (def Z (fn [f-maker] 70 | ((fn [self] (f-maker #((self self) %))) 71 | (fn [self] (f-maker #((self self) %)))))) 72 | ``` 73 | 74 | A helper function that might make it easier to read: `self-apply` (alias U 75 | combinator) is simply `(fn [x] (x x))`. 76 | 77 | ```clojure 78 | (def Z (fn [f-maker] 79 | (self-apply 80 | (fn [self] (f-maker #((self-apply self) %)))))) 81 | ``` 82 | 83 | The important thing to note above is that the `(self-apply self)` form inside, 84 | when called, will expand to the body of `Z` itself, _the very form with which we 85 | have started_. That is, `Z` calls `f-maker` with a box containing `Z`'s own 86 | body, and thus _loads it into the resulting function_. This is a - probably the - 87 | key step to understand here, the rest follows pretty trivially. If the "why" 88 | is not clear yet, just walk through the substitutions step by step; it is a 89 | crucial exercise. 90 | 91 | In the end, this results in getting a version of our original `f` (let it be 92 | `self-replicating-f`) that has the same body as `f`, except that it has the 93 | means to recreate itself on demand: at the point of recursion, it has the body 94 | of `Z`_, with_ `f-maker` _already bound_, boxed in, ready to be evaluated. 95 | 96 | Once that happens, i.e., when a recursive call is initiated in our supercharged 97 | `self-replicating-f`, the box opens, and the whole machinery inside starts 98 | moving again, with the body of `Z` - via calling `f-maker` - ultimately reducing 99 | itself to the same `self-replicating-f`, that is finally ready to take its 100 | argument (the one being passed in the recursive call) and execute. 101 | 102 | ```clojure 103 | ;; self-replicating-f 104 | (fn [x] ( 105 | ;; call `#((self-apply self) %)` i.e. `#(Z-body-with-f-maker-enclosed %)` 106 | ;; i.e. `#(self-replicating-f %)` at the point of recursion 107 | )) 108 | ``` 109 | 110 | And that's pretty much it. 111 | 112 | ```clojure 113 | (def self-replicating-f (Z f-maker)) 114 | ``` 115 | 116 | The Y combinator 117 | --- 118 | The Y combinator is the same as the Z combinator, except that it does not wrap 119 | the `(self-apply self)` call in a lambda, conveniently delaying evaluation. This 120 | only works in lazy languages though, like Haskell, where functions evaluate 121 | their arguments only when needed, and not before executing the body. Otherwise 122 | we'd be stuck in an infinite recursion - `(self-apply self)` would expand 123 | forever after the first call, before it could be passed on to `f-maker`. 124 | 125 | ```clojure 126 | (def Y (fn [f-maker] 127 | (self-apply 128 | (fn [self] (f-maker (self-apply self)))))) 129 | ``` 130 | 131 | TODO 132 | --- 133 | - [ ] Go through and summarize [McAdam's 134 | paper](http://www.lfcs.inf.ed.ac.uk/reports/97/ECS-LFCS-97-375/) on the 135 | practical applications of the concept. 136 | 137 | --------------------------------------------------------------------------------