9.5 KiB
Hoon 3: our first program
It's time for us to do some actual programming. In this section, we'll work through that classic Urbit pons asinorum, decrement.
If you learned Nock before Hoon, you've already done decrement.
If not, all you need to know is that the only arithmetic
intrinsic in Nock is increment -- in Hoon, the unary .+
rune.
So an actual decrement function is required.
In chapter 3, we write a decrement builder: more or less the simplest nontrivial Urbit program. We should be able to run this example:
~tasfyn-partyv:dojo/sandbox> +test 42
41
What's in that subject?
As we've seen, Hoon works by running a twig against a subject. We've been cheerfully running twigs through three chapters while avoiding the question: what's in the subject? To avoid the issue we've built a lot of constants, etc.
Of course your twig's subject comes from whoever runs it. There
is no one true subject. Our twigs on the command line are not
run against the same subject as our generator code, even though
they are both run by the same :dojo
appliance.
But the short answer is that both command-line and builder get basically the same subject: some ginormous noun containing all kinds of bells and whistles and slicers and dicers, including a kernel library which can needless to say decrement in its sleep.
As yet you have only faced human-sized nouns. We need not yet acquaint you with this mighty Yggdrasil, Mother of Trees. First we need to figure out what she could even be made of.
Clearing the subject
We'll start by clearing the subject:
:- %say |= * :- %noun
=> ~
[%hello %world]
The =>
rune ("tisran"), 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 =<
("tislit"). In chapter 1, when we wrote
+3:test
, we meant =>(test +3)
or =<(+3 test)
.)
What is this ~
? It's Hoon nil
, a zero atom with this span:
~tasfyn-partyv:dojo/sandbox> ?? ~
[%cube 0 %atom %n]
~
We use it for list terminators and the like. Obviously, since our old test code is just a constant, a null subject works fine:
~tasfyn-partyv:dojo/sandbox> +test
[%hello %world]
Getting an argument
Obviously, if we want to write a decrement builder, we'll have to
get an argument from the command line. This involves changing
the test.hoon
boilerplate a little:
:- %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 replacing a very interesting subject that
contains arg
with a very boring one that contains only arg
,
for the same reason we cleared the subject with ~
.
In case there's any doubt about the subject (.
is limb syntax
for +1
, ie, the whole noun):
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
=> arg=arg
.
~tasfyn-partyv:dojo/sandbox> +test 42
arg=42
We can even write a trivial increment function using .+
:
:- %say |= [* [[arg=@ud ~] ~]] :- %noun
=> arg=arg
+(arg)
~tasfyn-partyv:dojo/sandbox> +test 42
43
Below we'll skip both boilerplate lines in our examples.
A core is a code-data cell
But how do we actually, like, code? The algorithm for decrement is clear. We need to count up to 41. (How do we run useful programs on a computer with O(n) decrement? That's an implementation detail.)
We'll need another kind of noun: the core. Briefly, the core
is always a cell [battery payload]
. The payload is data, the
battery is code -- one or more Nock formulas, to be exact.
Consider a simple core with a one-formula battery. Remember, we create Nock formulas by compiling a twig against a subject. The subject is dynamic data, but its span is static. What span do we give the compiler, and what noun do we give the formula?
A core formula always has the core as its subject. The formula is essentially a computed attribute on the payload. But if the subject was just the payload, the formula couldn't recurse.
Of course, there is no need to restrict ourselves to one computed attribute. We can just stick a bunch of formulas together and call them a battery. The source twigs in this core are called "arms," which have labels just like the faces we saw earlier.
Hoon overloads computed attributes (arms) and literal attributes
(legs) in the same namespace. A label in a wing may refer to
either. To extend the name-resolution tree search described in
chapter 1, 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.
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.
This demands an example: if foo
produces some core c
, and
bar
is an arm in that c
(which may be foo
itself, or some
leg within foo
), bar.foo
runs the arm formula with c
as the
subject. You might think that moo.bar.foo
would compute
bar.foo
, then search for moo
within that result. Instead, it
searches for moo
within c
. (You can get the other result
with moo:bar.foo
.)
Does this sound too tricky? It should - it's about the most complicated feature of Hoon. It's all downhill once you understand cores.
Let's again extend our ++span
mold:
++ span
$% [%atom @tas]
[%cell span span]
[%core span (map ,@tas twig)]
[%cube * span]
[%face @tas span]
==
This definition of %core
is somewhat simplified from the
reality, but basically conveys it. (Moreover, this version of
span
describes every kind of noun we build.) In our %core
we
see a payload span and a name-to-twig arm table, as expected.
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."
Increment with a core
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)
.)
We can actually print out a core. Take out the =< inc
:
|%
++ inc
+(arg)
--
~tasfyn-partyv:dojo/sandbox> +test 42
!!!
~tasfyn-partyv:dojo/sandbox> ? +test 42
!!!
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
. Through the magic of labels, 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 actually a simpler way to write this. We've seen it already. It's not exactly a variable declaration:
=+ pre=0
.
~tasfyn-partyv:dojo/sandbox> +test 42
[pre=0 arg=42]
We actually decrement
Now we can write our actual decrement program:
=+ 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]
.
?:
is a regular rune which does exactly what you think it does.
Bear in mind, though, that in Hoon 0 (&
, "rob") is true and 1
(|
, "bar") is false.
The real action is in dec(pre +(pre))
. This is obviously an
irregular form -- it's the same mutation form we saw before.
Writing it out in full regular form:
=+ pre=0
=< dec
|%
++ dec
?: =(arg +(pre))
pre
%= dec
pre +(pre)
==
--
~tasfyn-partyv:dojo/sandbox> +test 42
41
%=
, "centis", is the rune which almost every use of a wing
resolves to. It might be called "evaluate with changes."
When we evaluate with changes, we take a wing (dec
) here and
evaluate it as described above. Searching in the subject, which
is of course our core, we find an arm called dec
and run it.
The changes (replacing pre
with +(pre)
) are always applied
relative to the core we landed on (or the leg we landed on).
The change wing is relative to this target; the subject of the
replacement (+(pre)
) is the original subject.
So, in English, we compute the dec
arm again, against a new
core with a new payload that contains an incremented pre
.
And thus, we decrement. Doesn't seem so hard, does it?