Cores

You learned what a core is in previous lessons. After a brief review, let's talk about how to use cores effectively in Hoon programming.

A core is a particularly useful kind of Hoon data: a cell whose head is a battery and whose tail is a payload:

          [core]
         /      \
  [battery]    [payload]

The battery is a series of arms, each of which is intended to be evaluated with the parent core as the subject. Some cores have samples and some don't. Those that do have a payload structured as a cell. The head is the sample and the tail is the context:

          [core]
         /      \
  [battery]    [payload]
               /       \
         [sample]     [context]

The context is all the relevant data needed to evaluate the arms in the battery correctly, apart from the sample. In cores with no sample, there is no distinction between 'payload' and 'context'.

In this lesson you'll learn how to produce various kinds of cores. You've already been using one kind of core, produced with the |= rune: gates. But in this lesson you'll learn how to produce many-armed cores, with and without samples. You'll also learn how to make use of these cores in your own programs.

Project Euler 1

Let's start with an example program illustrating the use of a many-armed core.

Problem 1 of Project Euler is as follows:

If we list all the natural numbers below 10 that are multiples of
3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

The program below provides a solution:

|=  a=@
=<  (euler-sum a)
::
|%
++  threes
  |=  a/@
  =|  b/@
  |-  ^-  @
  ?:  (lth a b)
    0
  (add b $(b (add 3 b)))
::
++  fives
  |=  a/@
  =|  b/@
  |-  ^-  @
  ?:  (lte a b)
    0
  ?:  =((mod b 3) 0)
    $(b (add b 5))
  (add b $(b (add b 5)))
::
++  euler-sum
  |=  a=@
  (add (threes a) (fives a))
--

Save this as euler1.hoon in the /gen directory of your urbit's pier and run it in the dojo:

> +euler1 1.000
233.168

Success! (We could use the same program to find the sum of the multiples of 3 and 5 for numbers other than 1.000 as well.)

Let's take a closer look at the code (with part of it elided).

|=  a=@
=<  (euler-sum a)
::
|%
      ...
--

This uses |= to produce a gate whose sample is an atom labeled a. The =< rune creates a new subject using |% and evaluates (euler-sum a) against that subject. euler-sum isn't a function in the Hoon standard library---it's a user-defined gate. This gate is only defined in the core created by the |% expression.

|% Produce a Core with No Sample

The |% rune produces a core with no sample; nearly always it's used to create a multi-arm core. The |% rune takes a series of arm definitions. The expression is terminated with the -- rune. From the above example:

|%
++  threes
  |=  a/@
  =|  b/@
  |-  ^-  @
  ?:  (lth a b)
    0
  (add b $(b (add 3 b)))
::
++  fives
  |=  a/@
  =|  b/@
  |-  ^-  @
  ?:  (lte a b)
    0
  ?:  =((mod b 3) 0)
    $(b (add b 5))
  (add b $(b (add b 5)))
::
++  euler-sum
  |=  a=@
  (add (threes a) (fives a))
--

As you can see, the expression begins with |%, ends with --, and in between it has three arm definitions. Each arm definition begins with ++ followed by an arm name. After each name is the Hoon expression for that arm. In this example, each arm defines a gate. Once this core is in the subject these gates can be called like any other standard library gate.

The |% rune produces a core with no sample. The composition of the head of this core, i.e., the battery, is determined by the arm definitions of the |% expression. The tail of the core, i.e., the payload, is the subject against which the |% expression was evaluated. This is important -- the arm definitions can use any data from the subject, and that data is guaranteed to be available to the arm regardless of the arm evaluation context. Each arm can make calls to any of the other arms in that core as well, because arms are evaluated with the entire parent core as the subject.

Cores and Types

The ++ rune is used for most arm definitions in |%, but there are other runes for special kinds of arms.

Defining Types with Core Arms

Let's say that you want to define several types and make use of them throughout your Hoon program. One common way to do this involves producing a core that defines these types and keeping it in the subject. Each arm of such a core defines a type. Arms that define types are created with the +$ rune instead of ++.

Let's write such a core and bind it to a face in the dojo. (Feel free to cut and paste the core definition for t below.)

> =t |%
    +$  integer  @
    +$  integer-pair  [integer integer]
    +$  tagged-pair
      $%  [%abc integer-pair]
          [%def integer-pair]
    ==
  --

> t
< 3.axs
  { {our/@p now/@da eny/@uvJ}
    <19.min 24.fmf 7.mpi 38.ard 119.spd 241.plj 51.zox 93.pqh 74.dbd 1.qct $141>
  }
>

We now have a core that defines three types: integer, which is essentially just an alias for @; integer-pair which is the type for a pair of integers, and tagged-pair which is an integer-pair in a cell, with one of two possible tags as the head of that cell, %abc or %def. Let's do some casting in the dojo using the types of this core:

> `integer.t`15
15

> `integer-pair.t`[15 20]
[15 20]

> `tagged-pair.t`[%abc [15 20]]
[%abc 15 20]

> `integer.t`[15 20]
nest-fail

> `integer-pair.t`15
nest-fail

Unbind t in the dojo subject as follows:

> =t

Type Constructor Arms

(list @) is a type. So is (list ^), (list tape), (list ?), etc. These are all lists of one sort or another, but they're all distinct types. You can think of list as a function that takes one type and returns another -- it takes, e.g., @ and returns an atom list type. We may call list one kind of type constructor.

A type constructor takes one or more types, and constructs another type from them. You can define your own type constructors using core arms.

To define a type constructor with an arm, use the +* rune. This rune is followed by (1) an arm name, (2) one or more faces naming the input types inside square brackets, and (3) a type expression that must evaluate to a structure.

To illustrate, let's make a type constructor that makes a pair of some input types. E.g., if given @, it returns the type [@ @].

> =t |%
    +*  pair-of  [a]  [a a]
  --

> `(pair-of.t @)`[12 14]
[12 14]

> `(pair-of.t @)`[12 14 17]
nest-fail

Let's try with other input types:

> `(pair-of.t ?)`[& |]
[%.y %.n]

> `(pair-of.t ^)`[[10 11] [12 13]]
[[10 11] 12 13]

> `(pair-of.t ^)`[[10 11] 12]
nest-fail

Unbind t again:

> =t

Cores with Samples (Doors)

Cores without samples are often useful, but sometimes more is desired. Sometimes you'll want to use a core with a sample. Such cores are called doors.

One way they can be used is to produce different versions of a function. There is a door in the Hoon standard library, ++ rs, for producing single-precision float functions. The various arms of this door (e.g., add, sub, mul) are designed for use with atoms whose aura is @rs:

> (add:rs .3.14 .2.72)
.5.86

> (sub:rs .3.14 .2.72)
.4.2000008e-1

> (mul:rs .3.14 .2.72)
.8.5408

But there are different versions of these gates for different rounding rules. The default rule is to round to zero. Accordingly, when add is evaluated with rs as the subject, a gate is produced that adds single-precision floats with that rounding rule assumed.

You can change the rounding rule by passing an argument to the ++ rs core:

> ~(add rs %u)
< 1.hsu
  {{a/@rs b/@rs} <21.fan {r/?($n $u $d $z) <51.zox 93.pqh 74.dbd 1.qct $141>}>}
>

> ~(add rs %d)
< 1.hsu
  {{a/@rs b/@rs} <21.fan {r/?($n $u $d $z) <51.zox 93.pqh 74.dbd 1.qct $141>}>}
>

Each of these expressions produces a version of the add gate from rs. By using ~( ) to pass %u to rs before producing add, you're creating an add gate for floats that rounds up. By passing %d, you're producing an add gate for floats that rounds down:

> (~(add rs %u) .3.14159265 .1.11111111)
.4.252704

> (~(add rs %d) .3.14159265 .1.11111111)
.4.2527037

There are other uses for doors. Often you'll want a door to serve as a 'state machine'. That is, you'll want a core with data inside it representing various 'states' of the core. This mutable core data is stored in the core's sample. Usually the arms of such cores are designed to make use of the sample data, either by using the data to produce a value or by modifying the sample data.

Doors can be used a lot like objects in object-oriented programming languages. The arms of doors are like computed attributes of objects.

|_ Produce a Door, i.e., a Core with a Sample

The |_ rune is for producing a door. Syntactically it's like the |% rune, but it takes an extra subexpression to define the sample. The first subexpression following the |_ defines the sample; then there is a series of arm definitions. This series is terminated with the -- rune.

For an example of a door 'in the wild', you can look at ++ rs in the hoon.hoon standard library here.

Let's look at a simpler door, one that can be used as a state machine:

|_  a/@
  ++  this  .
  ::
  ++  inc
    this(a +(a))
  ::
  ++  plus
    |=  b=@
    this(a (add a b))
  ::
  ++  reset
    this(a 0)
--

The door produced by this code has four arms and a sample a of the type @.

But what is each arm doing? To put it briefly, the inc arm adds 1 to the atom in the sample, the plus arm adds b to the sample, and the reset arm resets the sample value to 0.

But in order to make the door behave like a state machine, these arms can't simply evaluate to an atom. Each of the arms above evaluates to a core. Specifically, they each evaluate to a version of the parent core.

Remember, the subject of an arm is the core in which that arm is defined. The this arm evaluates simply to ., i.e., the unmodified parent core. inc evaluates to this, i.e., the parent core, but with the sample a modified to be +(a). plus evaluates to this except with the sample a modified to be (add a b). And reset evaluates to this but with the sample a set to 0.

To use a door as a state machine, put the door in the subject and modify that door using its arms. Let's do this in the dojo first, then we'll use a generator. Set the dojo face num to the |_ expression above. You can paste the multi-line code above directly into the dojo if you like:

> =num |_  a/@
    ++  this  .
    ::
    ++  inc
      this(a +(a))
    ::
    ++  plus
      |=  b=@
      this(a (add a b))
    ::
    ++  reset
      this(a 0)
  --

> num
< 4.awp
  { a/@
    {our/@p now/@da eny/@uvJ}
    <19.min 24.fmf 7.mpi 38.ard 119.spd 241.plj 51.zox 93.pqh 74.dbd 1.qct $141>
  }
>

> a.num
0

> =num inc.num

> a.num
1

> =num inc.num

> a.num
2

> =num (plus:num 100)

> a.num
102

> =num reset.num

> a.num
0

As you can see, you never actually 'change' the num core literally; you actually produce a variant of that core and set that as the new value of num. You can see the sample value of num using a.num.

Let's do the same sort of thing in a generator. Save the following as numcore.hoon in the /gen directory of your urbit's pier:

|=  c=@
=/  num                              ::  define the `num` core
  |_  a/@
    ++  this  .
    ::
    ++  inc
      this(a +(a))
    ::
    ++  plus
      |=  b=@
      this(a (add a b))
    ::
    ++  reset
      this(a 0)
  --
=.  num  inc.num                     ::  increment the sample
=.  num  inc.num                     ::  do it again
=.  num  (plus:num c)                ::  add `c` to the sample
=.  num  (plus:num c)                ::  do it again
a.num                                ::  produce the sample value

Now run it in the dojo:

> +numcore 10
22

> +numcore 100
202

> +numcore 500
1.002

Each of the =. expressions 'modifies' num by replacing the old num core with a different version of it. In this program we 'modify' num four times.

For more on how to use cores as state machines see the next lesson.

Next Lesson: Cores Again