on master help sign in
Merge pull request #504 from baudtack/master

two examples and a bash script

commit09c57fcf93f55d358e6bc078d57fcb3660c212a6 authorrmariani <robert@tlon.io> dateWed 22 May 2019, 14:54 (7 years ago) parent12b96abf0371f2
4 files changed+313
learn/hoon/hoon-tutorial/recursion.md+193 learn/hoon/workbook/ackermann.md+18 learn/hoon/workbook/bank.md+79 weights.sh+23
learn/hoon/hoon-tutorial/recursion.mdnew file+193
@@ -0,0 +1,193 @@
1++++
2+title = "Recursion"
3+weight = 0
4+template = "doc.html"
5++++
6+
7+Recursion is a common pattern for solving certain problems in most programming
8+languages, and Hoon is no exception. One of the classically recursive problems
9+is that of factorial. The factorial of _n_ is the product of all positive
10+integers less than or equal to N. Thus the factorial of 5, denoted as _5!_,
11+would be:
12+
13+```
14+5 * 4 * 3 * 2 * 1 = 120
15+```
16+
17+### Factorial In Hoon
18+
19+Let's implement a factorial calculator in Hoon. To do so, we'll write a
20+recursive `gate` (Hoon's equivalent of a function) to perform the relevant
21+computation. Save the code below as `factorial.hoon` in your ship's `/gen`
22+directory.
23+
24+```
25+|= n=@ud
26+?: =(n 1)
27+ 1
28+(mul n $(n (dec n)))
29+```
30+
31+Our `gate` takes one sample (argument) `n` that must nest inside `@ud`, the
32+unsigned-integer type.
33+
34+Next we check to see if `n` is `1`. If so, the result is just `1`, since
35+`1 * 1 = 1`.
36+
37+If, however, `n` is _not_ `1`, then we branch to the final line of the code,
38+`(mul n $(n (dec n)))`, where the recursion logic lives. Here, we multiply `n`
39+by the recursion of `n` minus `1`. `$` initiates recursion: it calls the gate
40+that we're already in, but replaces its sample.
41+
42+In our example, we multiply `n` with the product of this _entire gate
43+all over again_ with `$(n (dec n))`, except when that new gate has its sample
44+decremented by one. This works recursively because each new gate will, of
45+course, itself contain the code to call a further-decremented gate. Gates will
46+continue to call new, further-decremented gates until `n` is `1`, and that `1`
47+will be the final number to be multiplied by.
48+
49+Let's run the program in the Dojo:
50+
51+```
52+> +factorial 5
53+120
54+```
55+
56+It may help to visualize the operation of our example gate. The pseudo-Hoon below
57+illustrates what happens when we use it to find the factorial of 5:
58+
59+```
60+(factorial 5)
61+(mul 5 (factorial 4))
62+(mul 5 (mul 4 (factorial 3)))
63+(mul 5 (mul 4 (mul 3 (factorial 2))))
64+(mul 5 (mul 4 (mul 3 (mul 2 (factorial 1)))))
65+(mul 5 (mul 4 (mul 3 (mul 2 1))))
66+(mul 5 (mul 4 (mul 3 2)))
67+(mul 5 (mul 4 6))
68+(mul 5 24)
69+120
70+```
71+
72+It's easy to see how we're "floating" gate calls until we reach the final
73+iteration of such calls that only produces a value. The `mul n` component of the
74+gate leaves something like `mul 5`, waiting for the final series of terms
75+to be operated upon. The `$(n (dec n))` component is what expands out the
76+expression, as illustrated by `(factorial 4)`. Once the expression cannot be
77+expanded out further, the operations work backwards, successively feeding values
78+into the `mul` functions behind them.
79+
80+##### Tail-Call Optimization
81+
82+Our last example isn't a very efficient use computing resources. The
83+pyramid-shaped illustration approximates what's happening on the **call stack**, a
84+memory structure that tracks the instructions of the program. In our example
85+code, every time a parent gate calls another gate, the gate being called is
86+"pushed" to the top of the stack in the form of a frame. This process continues
87+until a value is produced instead of a function, completing the stack.
88+
89+```
90+ Push order Pop order
91+(fifth frame) ^ |
92+(fourth frame) | |
93+(third frame) | |
94+(second frame) | |
95+(first frame) | V
96+```
97+
98+Once this stack of frames is completed, frames "pop" off the stack starting at
99+the top. When a frame is popped, it executes the contained gate and passes
100+produced data to the frame below it. This process continues until the stack
101+is empty, giving us the gate's output.
102+
103+When a program's final expression uses the stack in this way, it's considered to
104+be **not tail-recursive**. This usually happens when the last line of executable
105+code calls more than one gate, our example code's `(mul n $(n (dec n)))` being
106+such a case. That's because such an expression needs to hold each iteration of
107+`$(n (dec n)` in memory so that it can know what to run against the `mul`
108+function every time.
109+
110+To reiterate: if you have to manipulate the result of a recursion as the last
111+expression of your gate, as we did in our example, the function is not
112+tail-recursive, and therefore not very efficient with memory. A problem arises
113+when we try to recurse more times that we have space on the stack. This will
114+result in our computation failing and producing a stack overflow. If we tried
115+to find the factorial of `5.000.000`, for example, we would almost certainly
116+run out of stack space.
117+
118+But the Hoon compiler, like most compilers, is smart enough to notice when the
119+last statement of a parent can reuse the same frame instead of needing to
120+add new ones onto the stack. If we write our code properly, we can use a single
121+frame that simply has its values replaced with each recursion.
122+
123+##### A Tail-Recursive Gate
124+
125+With a bit of refactoring, we can write a version of our factorial gate that
126+_is_ tail-recursive and can take advantage of this feature:
127+
128+```
129+|= n=@ud
130+=/ t=@ud 1
131+|-
132+?: =(n 1)
133+ t
134+$(n (dec n), t (mul t n))
135+```
136+
137+The above code should look familiar. We are still building a gate that
138+takes one argument `n`. This time, however, we are also putting a face on a
139+`@ud` and setting it's initial value to 1. The `|-` here is used to create a new
140+gate with one arm `$` and immediately call it. Think of `|-` as the recursion
141+point.
142+
143+We then evaluate `n` to see if it is 1. If it is we return the value of `t`. In
144+the case that `n` is anything other than 1, we perform our recursion:
145+
146+```
147+$(n (dec n), t (mul t n))
148+```
149+
150+All we are doing here is recursing our new gate and modifying the values of `n`
151+and `t`. `t` is used as an accumulator variable that we use to keep a running
152+total for the factorial computation.
153+
154+Let's use pseudo-Hoon to illustrate how the stack is working in this example for
155+factorial 5.
156+
157+```
158+(factorial 5)
159+(|- 5 1)
160+(|- 4 5)
161+(|- 3 20)
162+(|- 2 60)
163+(|- 1 120)
164+120
165+```
166+
167+We simply multiply `t` and `n` to produce the new value of `t`, and then
168+decrement `n` before repeating. Since this `$` call is the final and solitary
169+thing that is run in the default case and since we are doing all computation
170+before the call, this version is properly tail-recursive. We don't need to do
171+anything to the result of the recursion except recurse it again. That means that
172+each iteration can be replaced instead of held in memory.
173+
174+##### A Note on `$`
175+
176+`$` (pronounced "buc") is, in its use with recursion, a reference to the gate that we are inside
177+of. That's because a gate is just a core with a single arm named `$`. The
178+subject is searched depth-first, head before tail, with faces skipped, and
179+stopping on the first result. In other words, the first match found in the head
180+will be returned. If you wished to refer to the outer `$` in this context, the
181+idiomatic way would be to use [`^$`](/docs/reference/rune/ket). The `^` operator
182+skips the first match of a name.
183+
184+### Exercises
185+
186+1. Write a recursive gate that produces the first _n_
187+[Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number)
188+
189+2. Write a recursive gate that produces a list of moves to solve the
190+[Tower of Hanoi problem](https://en.wikipedia.org/wiki/Tower_of_Hanoi).
191+Disks are stacked on a pole by decreasing order of size. Move all of the
192+disks from one pole to another with a third pole as a spare, moving one
193+disc at a time, without putting a larger disk on top of a smaller disk.
learn/hoon/workbook/ackermann.mdnew file+18
@@ -0,0 +1,18 @@
1+Write a gate to compute [Ackermann's Function](https://en.wikipedia.org/wiki/Ackermann_function)
2+
3+```
4+|= [m=@ n=@]
5+?~ m +(n)
6+?~ n $(m (dec m), n 1)
7+$(m (dec m), n $(n (dec n)))
8+```
9+
10+Ackermann Function is one of the earliest examples of a function that is totally computable, meaning it can be solved, but is not primitive recursive, meaning it can not be rewritten in an iterative fashion.
11+
12+Our gate takes two argument, `m` and `n`. There are three cases.
13+
14+First, if `m` is zero, return the increment of `n`.
15+Second, if `n` is zero, decrement `m`, set `n` to 1 and recurse.
16+Else, decrement `m` and set `n` to be the value of the Ackermann function with `n` and the decrement of `n` as arguments.
17+
18+This is not a terribly useful function to compute but it has an interesting history in mathematics. When running this function the value grows rapidly even for very small input. The value of computing this where `m` is `4` and `n` is `2` is an integer with 19,729 digits.
learn/hoon/workbook/bank.mdnew file+79
@@ -0,0 +1,79 @@
1++++
2+title = "Bank Account"
3+weight = 0
4+template = "doc.html"
5++++
6+
7+Write a door that can act as a bank account with the ability to withdraw, deposit, and check the balance.
8+
9+```
10+:- %say
11+|= *
12+:- %noun
13+=< =~ new-account
14+ (deposit 100)
15+ (deposit 100)
16+ (withdraw 50)
17+ balance
18+ ==
19+|%
20+++ new-account
21+ |_ balance=@ud
22+ ++ deposit
23+ |= amount=@ud
24+ +>.$(balance (add balance amount))
25+ ++ withdraw
26+ |= amount=@ud
27+ +>.$(balance (sub balance amount))
28+ --
29+--
30+```
31+
32+We start with the three lines we have in every `%say` generator.
33+
34+```
35+:- %say
36+|= *
37+:- %noun
38+```
39+
40+We're creating a cell the head of which is a `%say` and the tail of which is a gate that produces a cell with a head of the mark of the kind of data we are going to produce.
41+
42+```
43+=< =~ new-account
44+ (deposit 100)
45+ (deposit 100)
46+ (withdraw 50)
47+ balance
48+ ==
49+```
50+
51+Here we're going to compose two runes using `=<` which has inverted arguments. We use this rune to keep the heaviest twig to the bottom of the code.
52+
53+`=~` is a rune that composes multiple expressions. We take `new-account` and use that as the subject for the call to `deposit`. `deposit` and `withdraw` both produce a new version of the door which is used in subsequent calls which is why we are able to chain them in this fashion, the final reference is to `balance` which is the balance contain in the core we are about to examine.
54+
55+```
56+|%
57+++ new-account
58+ |_ balance=@ud
59+ ++ deposit
60+ |= amount=@ud
61+ +>.$(balance (add balance amount))
62+ ++ withdraw
63+ |= amount=@ud
64+ +>.$(balance (sub balance amount))
65+ --
66+--
67+```
68+
69+We've chosen here to wrap our door in it's own core to emulate the style of programming that is used when creating libraries. `new-account` is the name of our door. Recall that a door is a core with one or more arms that has a sample. Here our door has a sample of one `@ud` with the face `balance` and two arms, `deposit` and `withdraw`.
70+
71+Each of these arms produces a gate which takes an `@ud` argument. Each of these gates has a similar bit of code inside.
72+
73+```
74++>.$(balance (add balance amount))
75+```
76+
77+With wing syntax remember that we're looking for `+>`, or the 3rd element, out of `$`, which is to say the subject of the gate we are in, which is the entire `new-account` door. We change `balance` to be the result of adding `balance` and `amount` and produce the door as the result. `withdraw` functions the same way only doing subtraction instead of addition.
78+
79+The important point to notice here is that the sample, `balance`, is stored as part of the door rather than existing outside of it.
weights.shnew file+23
@@ -0,0 +1,23 @@
1+#!/bin/bash
2+
3+EDITOR=${EDITOR:-atom}
4+
5+# build weights file
6+for file in $(find -type f -name \*.md); do
7+ [[ $file == *_index.md ]] && continue
8+ echo $(grep -m 1 "weight = " $file | sed 's/^weight = //') $file
9+done | sort -n > weights
10+
11+# edit weights file
12+$EDITOR weights
13+read -p "Edit weights and press enter to continue..."
14+
15+# for each line in weights file, read values weight and file, and update
16+# $weight in $file
17+cat weights | while read weight file; do
18+ echo $weight $file
19+ sed -i'' "s/^weight = [.0-9]*/weight = $weight/" $file
20+done
21+
22+# remove weights file
23+rm weights