Lesson 4 – The Right to Bear Arms

Introduction


The generators we’ve seen to this point use |= to take a sample, and then evaluate a series of computations to produce a result.
We’ve seen that a gate is a one armed core, whose arm is named buc, where that arm $ is computed immediately.

The $ naming is standard, but arbitrary - all arms named $ will be computed immediately, and gates will always have an arm named buc. In other core constructions, we are free to create arms with other names of our choosing; but these will not be immediately computed, and must instead be called.

In this lesson we will flesh out a more complete definition of core. A core is data structure which contains one or more arms (methods).

Technically a core is a cell of [battery  payload]. The battery is a series of one or more arms, or computational steps. The payload is data needed for computation of the arms which, itself, is a cell of [sample  context].  The sample is defined explicitly, as we’ve already seen in the case of gates.  The context is defined implicitly as the subject when the core is evaluated.  The default subject includes all of the Hoon standard library.  Therefore, as cores are passed the subject as part of their payload, they will have access to all of the Hoon standard library by default.

Goal

Write a simple multi-armed core to produce some result.  For this example, let us take a sample of two @uds and produce a cell of both (1) the multiplication product of the two inputs and (2) the additive sum of the two inputs, using arms for those calculations.

Rune List

|% bar·cen

SyntaxSummary
|% ++  term  hoon ++  term  hoon -​-Creates a core, a Hoon data structure which contains one or more computational methods (arms).

Produces a core, a cell of [battery payload].

This type of core does not automatically come with an arm buc which is computed immediately, but it can contain many other arms, declared by one of the lus runes. In our work, we'll mostly use ++ and +$.

|% takes any number of children in pairs of term (arm name) and some hoon (code to be evaluated when arm is computed).

Each pair of term and hoon is preceded by ++ or another arm marker.

|% must be closed by the boundary --

Documentation on urbit.org

Exercise

Try this in dojo:

=babbys-first-core |%
++  add-two-and-two  (add 2 2)
++  add-two-more  (add add-two-and-two 2)
--
add-two-and-two.babbys-first-core
4
add-two-more.babbys-first-core
6

We have created a simple core, with two arms, `add-two-and-two` and `add-two-more` and assigned our core the face `babbys-first-core`.

We call the first arm by `add-two-and-two.babbys-first-core` (The dot operator here can be read “in”). As expected, 4 is returned.

Note that when we call the second arm `add-two-more.babbys-first-core`, the code within that arm can call any other arm in the core. Also note that within the core, we do not need `.babbys-first-core` as part of the search path; that is, add-two-more doesn’t need to specify add-two-and-two.babbys-first-core. This is because a core's arm take the parent core as the subject, thus when searching for `add-two-and-two`, the first place the compiler looks is in the core to which `add-two-more` belongs.

Exercise

Try this slightly less trivial core in dojo:

=rip-core |%
++  adder
  |=  [a=@ud b=@ud]
  (add a b)
++  multer
  |=  [c=@ud d=@ud]
  (mul c d)
--
(adder.rip-core 3 4)
7
(multer.rip-core 3 4)
12

Note that in this example, we're not just getting values back from arms - our arms are, themselves gates. We are able to (in the code (adder.rip-core 3 4)) call one of those gates, pass it some sample (arguments) and get back the result of the core's computation.

=< tis·gal

SyntaxSummary
=<  a=hoon  b=hoonThe product of a, given the product of b taken as the subject.
  • That is - compute b first, then compute a with all of b's information available.
  • This is difficult to understand. Unpacking further:
    • =< takes two children each of which is some hoon expression.
    • The first child will be computed after the second child has been evaluated, and where that second child will be used as subject.
    • The second child is computed first and used as the subject for computation of the first child.
Documentation on urbit.org

Exercise

Try this in dojo:

=<  b  [a='this' b='is the' c='subject']
'is the'

The first example is straight forward enough. We are asking for `b` from the subject given.

Exercise

But see:

=<  (add a b)  [a=6 b=7 c=12]
-find.add

Our second example fails with a -find.add error.  That is because the Hoon standard library is not available in our temporary subject.  The entire subject is the cell [a=5 b=7 c=12].

We know that our default dojo subject always includes the hoon standard library in it’s tail. Do dot in the dojo. Note that your subject is something like:
[[our=~zod now=a date eny=an entropy value]
<20.ike 31.qbf 9.ueb 36.oqd 94.qnb 247.yaz 51.pkf 129.ono 41.mac 1.ane $141>]

Remember that the funny number.abc information at the end is actually the hoon.hoon standard library. As the tail of the current subject can be called with +, we can fix the above example by adding + to our temporary subject.

Exercise

Try this in dojo:

=<  (add a b)  [+ a=5 b=7 c=12]
12

|^ bar·ket

SyntaxSummary
|^ hoon ++ term hoon ... ++ term hoon --|^ syntax is a combination of the |= rune and the |% rune:
  • The first child is some hoon expression which defines the core’s $ arm, which is computed immediately, just as in a |= gate.
  • The remaining expressions must be pairs of a term and some hoon, marked by ++ or another arm marker, just as in the |% core
  • |^ must be closed by the boundary --, just as in the |% core
The $ arm is evaluated immediately after creation of the core.

Produce a core whose battery includes an arm named buc, which is evaluated immediately, and at least one additional arm (named something else).

Documentation on urbit.org

Exercise

Try this in dojo:

|^  (another-trivial-arm 2)
++  another-trivial-arm
  |=  a=@ud
  (add a 2)
--
4

Required Concepts

A core is a cell of [battery payload]:

The battery includes the arms of the core, these are the core’s computational methods.
The payload is a cell of [sample context], i.e. the data required to run the instructions of the program
The sample is input, as we see when we open a gate
The context is the subject at the time the core is evaluated.

Walkthrough

Backstep Indentation (Proper) Walking Indentation (Improper)
|=  [x=@ud y=@ud]
|^  ^-  [@ud @ud]
:-  (arm-one x y)
(arm-two x y)
++  arm-one
  |=  [a=@ud b=@ud]
  (mul a b)
++  arm-two
  |=  [a=@ud b=@ud]
  (add a b)
--
|=  [x=@ud y=@ud]
 |^
  ^-
   [@ud @ud]
   :-
    (arm-one x y)
    (arm-two x y)
  ++  arm-one
   |=
    [a=@ud b=@ud]
    (mul a b)
  ++  arm-two
   |=
    [a=@ud b=@ud]
    (add a b)
  --

Demo:

+lesson-four [3 3]
[9 6]
  • |= [x=@ud y=@ud]
    |= creates a gate that requests a cell of two arguments, x and y, both of which are @uds.

    • |^ ^- [@ud @ud]
      |^ creates a core within the buc arm of our |= gate.
      ^-[@ud @ud]

      • :- (arm-one x y) (arm-two x y)
        :- creates a duple cell
        The first child (head of the cell) is a call to the gate `arm-one`, which passes x and y as a sample to that gate.
        The second child (tail of the cell) is a call to the gate `arm-two`, which again passes x and y as samples to that gate.

        • ++ arm-one
          |= [a=@ud b=@ud]
          (mul a b)
          ++ creates an arm in our |^ core, called `arm-one`
          This arm, `arm-one`, itself forms a |= that takes a cell of two arguments, a and b, both of which must be @uds.
          The gate takes those two arguments (a and b) and computes their product using the standard library function call `mul`.
        • ++ arm-two
          |= [a=@ud b=@ud]
          (add a b)
          ++ creates an arm in our |^ core, called `arm-two`
          This arm, `arm-two`, itself forms a |= that takes a cell of two arguments, a and b, both of which must be @uds.
          The gate takes those two arguments (a and b) and computes their sum using the standard library function call `add`.

      --
      -- closes the |^ core.

Flow:

  1. Create a gate which takes a sample of [@ud @ud]
    |= [x=@ud y=@ud]
  2. Evaluate the buc arm of the gate
    The $ arm of the |= gate is the following hoon expression, which includes the rest of our program starting at the |^
  3. Create a core, and cast its output as a cell of @uds.
    |^ ^- [@ud @ud]
    Note that the entire core is compiled before any arm (even $ is evaluated. This makes all of the arms defined after $ available during the computation of $, which is how we can form our cell using those arms.
  4. Create a cell of two values
    :- (arm-one x y)
    (arm-two x y)
    This cell will call both of the following arms (arm-one and arm-two) and pass them the arguments of the initial gate (x and y). Those arms will then be computed, and returned as the head and the tail of the cell, respectively).
    Note :- cannot produce a cell until each of its children have been evaluated. Both must be computed before we can return a product
  5. Evaluate (arm-one x y)
    This call is effectively the same as calling a file arm-one.hoon saved in our home/gen directory, that includes only the gate formed under arm-one:
    |= [a=@ud b=@ud]
    (mul a b)
    The gate that arm-one forms is computed with the passed arguments, and the product is returned.
  6. Evaluate (arm-two x y)
    As with the step above, arm-two is called and passed two arguments. The gate that arm-two forms is computed with the passed arguments and the sum is returned.
  7. Return the Product
    The cell of the now-computed values of (arm-one x y) and (arm-two x y) is output as the result of the |^ core.
    The arm $ of the |= gate is just the |^ core, so the output of |^ will be the output of the |= gate.
    We have already cast the output of the |^ core as [@ud @ud], so we needn't do it again for the gate.

Additional Reading for this lesson:

Homework


Please submit your work to us by the deadline on the Schedule (see schedule in the dropdown menu above) via this form.  Please submit your homework with all exercises from one week (two lessons) as a single pastebin or code-sharing platform of your choice.

  1. Write a generator that takes a sample of 3 arguments - two @uds and an @tas (which will be either %mul %sub %add %div), with arms to handle multiplication, division, addition and subtraction.  Use a gate and a |^ core.  Use ?  runes to branch for each @tas case.  Use the standard library functions to do the actual work in those arms.  Confirm that you can input your three arguments and receive the desired result of the selected operation
    • Bonus points!  Use ?- or ?+ to determine which operation to call using your @tas
    • Bonus points!  Write exception handling to avoid subtraction underflow