Polymorphism is a programming concept that allows a piece of code to use different types at different times. It's a common technique in most languages to make code that can be reused for many different situations, and Hoon is no exception. Let's take a look at a gate from the Hoon standard library as an example; we'll be passing a few different types in. The code below is an excerpt from
hoon.hoon and, as such, will not run as-is by itself.
fold is a wet gate that takes two arguments and produces a dry gate. But what are wet and dry gates?
A dry gate (also simply a gate) is the kind that you're already familiar with by now: a one-armed core with a sample. A wet gate is also a one-armed core with a sample, but there is a difference. With a dry gate, when you pass in an argument and the code gets compiled, the type system will try to cast to the type specified by the gate; if you pass something that does not fit in the specified type, for example a
cord instead of a
cell you will get a nest failure. On the other hand, when you pass arguments to a wet gate, their types are preserved and type analysis is done at the definition site of the gate -- in our case,
hoon.hoon -- rather than the call site.
++ fold |* [state=mold elem=mold] |= [[st=state xs=(list elem)] f=$-([state elem] state)] ^- state |- ?~ xs st $(xs t.xs, st (f st i.xs))
On the first line of the arm, we use
|* to create a wet gate, with two arguments:
elem. These arguments are both
molds; that is to say, they are type definitions. We use this to define the types used in the dry gate.
|= [[st=state xs=(list elem)] f=$-([state elem] state)]
Here we begin to define our dry gate. The first thing the dry gate takes is a cell,
[st=state xs=(list elem)].
state is the type we passed in first to our wet gate, and will be the type that is produced by the dry gate.
st is a value of type
state that will be used as an accumulator, and will be the final value returned when the dry gate is called.
The second argument is
f=$-([state elem] state).
$- is a rune that takes two type arguments,
[state elem] and
state in this case, and produces a
mold of a gate that maps from the first type operand to the second. This will match a gate we provide
fold for how to map from both the
The rest of the dry gate is straightforward:
^- state |- ?~ xs st $(xs t.xs, st (f st i.xs))
We ensure the output is of type
state. Then we check if
xs, the list, is empty. If it is, we return
st. Otherwise, we call
i.xs, the head of the list, and set
xs to be the tail of the list and repeat.
Lets look at two examples of using
%+ (fold (list @) @) :- ~ ~[1 2 3 4] |= [s=(list @) e=@] :_ s (add 2 e)
Here we have a call to
fold that we can see will produce a
(list @) from a list of
@. The first argument is a cell of
~[1 2 3 4].
The gate will simply add two to each element
e and append that to the front of
s. Let's run this in the
> %+ (fold (list @) @) :- ~ ~[1 2 3 4] |= [s=(list @) e=@] :_ s (add 2 e) ~[6 5 4 3] ~zod:dojo>
The list is in reverse order simply because it's easier to add to the front of a list than the end. If you needed it in the same order, you could just
fold does not have to produce a
list. Let's look at another example:
%+ (fold @ @) [0 ~[1 2 3 4]] |= [s=@ e=@] (add e s)
fold will produce a gate that takes an
atom and applies a gate to a
atoms. The difference here is that this call will produce a sum of the elements of the list, rather than the list itself.
The key takeaway from both of these examples is that the gates provided are iron polymorphic with respect to the definition of the type in
fold. They are iron polymorphic because samples
e nest under the types
elem. In second case case, that's because when we provided those to
fold, it was was stated they were
@. In the first case, we stated that
(list @) and
@. In both cases, the sample of each gate nest inside the types defined when we called the wet gate