9.9 KiB
Hoon 101.3: an algorithm
In chapter 0, we read about nouns. In chapter 1, we discovered twigs and legs. In chapter 2, we learned Hoon syntax and created our first source file.
Now it's time for an actual, meaningful algorithm.
How to use this tutorial
Ideally, you've installed an Urbit planet (if you have a ticket) or comet (if you don't). See the user doc.
We recommend opening up the dojo and just typing the examples; you don't know a language until you know it in your fingers. Also, make sure you've worked through the chapters in order.
Goal: a decrement generator
Our algorithm is the classic Urbit example: decrement.
If you learned Nock before Hoon, you've already written decrement. If not, all you need to know is that the only built-in arithmetic operator in Nock is increment. To decrement, we need to count up to the result with a simple loop.
A practical subject
As we've seen, Hoon works by running a twig against a subject. In chapter 1, we made a simple test subject that was just a few constants, and applied some simple twigs to it.
This time, we'll actually put useful working data in the subject, building up more and more complex subjects as we go.
Subject: nil
We'll start with the empty subject ~
:
:- %say |= * :- %noun
=> ~
[%hello %world]
The =>
rune ("tisgar", %tsgr
)), for =>(p q)
executes p
against the subject, then uses that product as the subject of
q
.
We've already used an irregular form of
=>
, or to be more precise its mirror=<
("tisgal",%tsgl
). In chapter 1, when we wrote+3:test
, we meant=>(test +3)
or=<(+3 test)
.
What is ~
? It's Hoon nil
, a zero atom:
~tasfyn-partyv:dojo/sandbox> ?? ~
[%cube 0 [%atom %n]]
~
We use ~
for list terminators and the like. Obviously, since
[%hello %world]
is just a constant, a nil subject works as
well as any:
~tasfyn-partyv:dojo/sandbox> +test
[%hello %world]
Subject: arg=@
Above, we used an empty subject to avoid the very complex, interesting subject your generator twig gets by default.
But a decrement generator needs to get an argument from the command line -- the number we're trying to decrement.
This involves changing the test.hoon
boilerplate a little.
Again, don't worry about the boilerplate line just yet:
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
=> arg=arg
[%hello arg]
~tasfyn-partyv:dojo/sandbox> +test 42
[%hello 42]
=> arg=arg
looks a little odd. We wouldn't ordinarily do this; we're just trying to keep the subject simple.
In case there's any doubt about the subject (remember from
chapter 1 that .
means +1
, the whole subject):
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
=> arg=arg
.
~tasfyn-partyv:dojo/sandbox> +test 42
arg=42
An increment generator
We can even write a trivial increment generator using .+
,
or even better its irregular form +
:
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
=> arg=arg
+(arg)
~tasfyn-partyv:dojo/sandbox> +test 42
43
We'll assume these same first two lines for the rest of the examples in this chapter.
Cores: one more kind of span
To decrement, we need a loop. To write a loop, we need yet
another kind of noun: the core. Briefly, a core is a [code data]
pair. Our new span
mold:
++ span
$% [%atom p=@tas]
[%cell p=span p=span]
[%core p=span q=(map ,@tas twig)]
[%cube p=* q=span]
[%face p=@tas q=span]
==
Structure of a core
The core is a cell [battery payload]
. The payload is data, the
battery is code -- a tree of Nock formulas.
In the %core
span, the payload is described by an arbitrary
span. The battery is described by a map of symbol to twig.
Yes, the span contains the complete AST of the whole core. This is a very different approach from most typed languages, which compute function signatures. To infer the product of an arm, we retrace the code in theory, but cache in practice.
Arms are computed attributes
How do we use a core? Remember that Nock is a function
Nock(subject formula) -> product
To activate a core, we pull one formula from the battery, and
execute with the whole core as the subject. Coincidentally,
Nock has one macro instruction, 9
, that does just this.
So the formula defines an arbitrary function on the core. The name of this function (as defined in the twig map) is a computed attribute, or arm.
Is a core an object? Not quite, because an arm is not a method. Methods in an OO language have arguments. Arms are functions only of the payload. (A method in Hoon is an arm that produces a gate, which is another core -- but we're getting too far ahead.) However, the battery does look a lot like a classic "vtable."
Arms and wing resolution
It might be a good time to brush up on your chapter 1. The wing resolution algorithm gets a little more complicated.
Hoon overloads computed attributes (arms) and literal attributes
(legs) in the same namespace. A label in a wing may refer to
either. when searching a core, we look for a matching arm. If
we find it we're done. If we don't, or if a ^
mark makes us
skip, we search into the payload.
Only the last limb in the wing can activate an arm. If a name resolves to a core arm, but it's not the last limb in the wing, the arm produces the core itself. Similarly, when the wing is not an access but a mutation, the arm refers to the core. Only the end of the wing is activated.
Suppose
foo
is a core.bar
is an arm on foo. Thenbar.foo
computes thebar
arm. Perhaps the product is another core. Even ifmoo
is an arm in this new core,bar.foo
, the wingmoo.bar.foo
does not computemoo
on the corebar.foo
. Instead, it looks formoo
within the payload of the corefoo
. (You're looking formoo:bar.foo
, ie,=>(bar.foo moo)
.)
Does this sound too tricky? Hopefully not, but it's about the most complicated semantic rule you'll find in Hoon.
Increment with a core
Keeping our two-line boilerplate, let's increment with a core:
=< inc
|%
++ inc
+(arg)
--
~tasfyn-partyv:dojo/sandbox> +test 42
43
What's going on? We used the |%
rune ("barcen") to produce a
core. (There are a lot of runes which create cores; they all
start with |
, and are basically macros that turn into |%
.)
The payload of a core produced with |%
is the subject with
which |%
is compiled. We might say that |%
wraps a core
around its subject. In this case, the subject of the |%
,
and thus payload, is our arg=@ud
argument.
Then we used this core as the subject of the simple wing inc
.
Remember that
=<(a b)
is just=>(b a)
. The core is heavy andinc
is light, so we use=<
to put the heavy part last.
The prettyprinter can even print a core, sort of. Take out the
=< inc
:
|%
++ inc
+(arg)
--
~tasfyn-partyv:dojo/sandbox> +test 42
<1.bgq arg=42>
~tasfyn-partyv:dojo/sandbox> ? +test 42
<1.bgq arg=@ud>
<1.bgq arg=42>
In this notation, 1
means the number of arms in the core, bgq
is a very short fingerprint, and arg=42
is the payload (and
arg=@ud
is the payload span).
Cores can be large and complex, and we obviously can't render all the data in them, either when printing a type or a value. At some point, you'll probably make the mistake of printing a big core, maybe even the whole kernel, as an untyped noun. Just press ^C.
Adding a counter
To decrement, we need to count up to the argument. So we need a counter in our subject, because where else would it go?
Let's change the subject to add a counter, pre
:
=> [pre=0 .]
=< inc
|%
++ inc
+(arg)
--
~tasfyn-partyv:dojo/sandbox> +test 42
43
Once again, .
is the whole subject, so we're wrapping it in a
cell whose head is pre=0
. This doesn't change the way we use
arg
, even though it's one level deeper in the subject tree.
Let's look at the subject again:
=> [pre=0 .]
.
~tasfyn-partyv:dojo/sandbox> +test 42
[pre=0 arg=42]
~tasfyn-partyv:dojo/sandbox> ? +test 42
[pre=@ud arg=@ud]
[pre=0 arg=42]
There's a less cluttered way to write =>([a .] b)
. In wide
mode, =+(a b)
. A tall example:
=+ pre=0
.
~tasfyn-partyv:dojo/sandbox> +test 42
[pre=0 arg=42]
This rune =+
, "tislus", %tsls
, is of course the "variable
declaration" we saw in chapter 2.
We actually decrement
Now we can write our actual decrement program, combining the features we've explored above:
=+ pre=0
=< dec
|%
++ dec
?: =(arg +(pre))
pre
dec(pre +(pre))
--
~tasfyn-partyv:dojo/sandbox> +test 42
41
=(a b)
is an irregular form of .=(a b)
, ie, "dottis" or the
noun [%dtts a b]
. Likewise, +(a)
is .+(a)
, ie, "dotlus"
or [%dtls a]
.
?:
, "wuttis", %wtts
, does exactly what it does in C.
The real action is in dec(pre +(pre))
. We saw this same
mutation form in chapter 1. It's an irregular form of the %=
rune, "centis", %cnts
.
Here, of course, we're computing an arm of a mutated core. We're
recomputing the loop arm, dec
, against a new core with an
incremented pre
leg. Basically, everything interesting in Hoon
happens when you compute an arm of a mutated core.
The whole program, using only regular forms, wide and tall:
=+ ^=(pre 0)
=< dec
|%
++ dec
?: .=(arg .+(pre))
pre
%= dec
pre .+(pre)
==
--
~tasfyn-partyv:dojo/sandbox> +test 42
41
A good illustration of why we need irregular forms.
Progress
Now we've actually done something useful. Well, if you count O(n) decrement as something useful.
We've actually seen most of the major tools in Hoon's toolbox, just in a super-minimalist selection. But we'll need a few more runes to get to something that looks like real programming.
For instance, we have a decrement algorithm, but we don't have a
decrement function - in the Hoon sense of the word, anyway. We
don't see (dec arg)
in this code. That'll be the next chapter.