Lesson 10 – Finally, FUNctional Programming
Introduction
Today we’re going to build something that might actually be of use to you in your daily life, a length measurement converter. We’ll add a few new tools, but also we’ll revisit many of the things we’ve learned along the way, including maps, arms of a core, and casting our results. This generator may look long, but it’s not as complicated as it seems. Let’s get started, then.
Goal
Write a generator to take a input measurement unit, an @rs value, and an output unit to which we will convert the input measurement. For example, this generator could convert a number of imperial feet to metric decameters.Rune list
?&
(wutpam)
A conditional that checks a list of hoon conditions and results in %.y if both/all of them are true, and otherwise results in a %.n
~(“tape” !!)
(sigbar and zapzap, respectively)
A stack tracing printf producing an error message, then crashing (!!). This allows you to give a human legible error to your user if the program fails for some reason
+$
(lusbuc)
Defines a type using one of the buc runes  creation of custom types. This is difficult to demonstrate in dojo, but it will make sense in our example (we promise!).
@rs
Atomic type for singleprecision floating point numbers. All atoms in hoon are fundementally integers, however Auras allow us to encode text, dates, and other information as atoms. A singleprecision floating point uses 32 bits to represent a decimal number. An @rs literal always begins with a .
The literal
Generator
!.
= [frmeas=@tas num=@rs tomeas=@tas]
=<
^ @rs
?. (check frmeas tomeas)
~("Invalid Measures" !!)
(output (meters frmeas num) tomeas)
::
%
+$ allowed ?(%inch %foot %yard %furlong %chain %link %rod %fathom %shackle %cable %nauticalmile %hand %span %cubit %ell %bolt %league %megalithicyard %smoot %barleycorn %poppyseed %atto %femto %pico %nano %micro %milli %centi %deci %meter %deca %hecto %kilo %mega %giga %tera %peta %exa)
::
++ check
= [frmeas=@tas tomeas=@tas]
&(?=(allowed frmeas) ?=(allowed tomeas))
::
++ meters
= [in=@tas value=@rs]
=/ factorone
(~(got by converttomap) in)
(mul:rs value factorone)
::
++ output
= [in=@rs out=@tas]
?: =(out %meter)
in
(div:rs in (~(got by converttomap) out))
::
++ converttomap
^ (map @tas @rs)
% my
:~ : %atto .1e18
: %femto .1e15
: %pico .1e12
: %nano .1e8
: %micro .1e6
: %milli .1e3
: %poppyseed .2.212e2
: %barleycorn .8.47e2
: %centi .1e2
: %inch .2.54e2
: %deci .1e1
: %hand .1.016e1
: %link .2.012e1
: %span .2.228e1
: %foot .3.048e1
: %cubit .4.472e1
: %megalithicyard .8.291e1
: %yard .9.145e1
: %ell .1.143
: %smoot .1.7
: %fathom .1.83
: %rod .5.03
: %deca .1e1
: %chain .2.012e1
: %shackle .2.743e1
: %bolt .3.658e1
: %hecto .1e2
: %cable .1.8532e2
: %furlong .2.0117e2
: %kilo .1e3
: %mile .1.609e3
: %nauticalmile .1.850e3
: %league .4.830e3
: %mega .1e6
: %giga .1e8
: %tera .1e12
: %peta .1e15
: %exa .1e18
: %meter .1
==

Walkthrough
!.
!. disables stack tracing for the following expression. We use it here since we've already written an error message for the one error case.=
= Creates a gate, taking two children  a sample and some hoon to evaluate.
[frmeas=@tas num=@rs tomeas=@tas]
The first child of =, our type specification for our sample, a cell of [@tas @rs @tas]  The second child of = is the following expression, which includes the rest of our code:
=<
=< inverts two hoon statements, and evaluates the first in light of the second. This is necessary in order to have our % core in the subject.^
@rs
^ casts our results as an @rs  we will be converting one measure to another using fractional values where appropriate, so we must use @rs (or another floating point type)
?.
Conditionally branch based on the true/false value of the first child hoon.(check frmeas tomeas)
Use the ++check arm to verify that our input and output units are valid.
~("Invalid Measures" !!)
If ++check returns false, crash with error message.
Note that since we used !. above, the rest of the stack trace will not be printed. (output (meters frmeas num) tomeas)
Call the ++output arm, which takes two children.
The first child must be a value in meters, which is exactly what is returned by(meters frmeas num)
. The second child is the desired output unittomeas
.
This entire expression is the first child of =<. It will be evaluated with the product of the following expression taken as the subject.

%
% creates a core with a number of arms and is terminated by the boundary  .+$ allowed
+$ indicates a typeconstructing arm named allowed?(%inch %foot %yard %furlong %chain %link %rod %fathom %shackle %cable %nauticalmile %hand %span %cubit %ell %bolt %league %megalithicyard %smoot %barleycorn %poppyseed %atto %femto %pico %nano %micro %milli %centi %deci %meter %deca %hecto %kilo %mega %giga %tera %peta %exa)
Our type, allowed, is defined as the union of the types above.
++ check
++ marks an arm named check.=
= creates a gate and takes two children.[frmeas=@tas tomeas=@tas]
The first child of = specifies the sample type as a cell of [@tas @tas] These are our input unit and output unit.&(?=(allowed frmeas) ?=(allowed tomeas))
If both frmeas and tomeas nest under allowed, return true. Otherwise, return false.
++ meters
++ marks an arm named meters.=
= creates a gate and takes two children.[in=@tas value=@rs]
The first child of = specifies the sample type as a cell of[@tas @rs]=/
Pin a noun to the subjectfactorone
with face factorone(~(got by converttomap) in)
and value produced by looking up our input type in in our map. Recall from our last lesson that this is the irregular form of %~ calling the arm got of the door by(mul:rs value factorone)
Use the Standard Library function mul:rs (note this is different than the multiply function used for @uds) to multiply the incoming @rs from the gate’s sample and the @rs found by factorone’s map search.
The purpose of this arm is to convert our incoming measurement into meters, which is our standard unit, before converting to our desired output unit.
++ output
++ marks an arm named output.=
= creates a gate and takes two children.[in=@rs out=@tas]
Bartis creates a gate that takes a cell of two arguments, an @rs and an @tas?: Branch based on the value of the first child.
=(out %meter)
If our output type is %meter:in
Return in (which is our value, already in meters).(div:rs in (~(got by converttomap) out))
Otherwise, divide in by the conversion factor obtained by map lookup of our output type.
++ converttomap
++ marks an arm named converttomap.^
Takes two children, a type and some hoon.(map @tas @rs)
Our type specification, a map of @tas and @rs pairs.%
Call the gate my, which takes a nullterminated cell of ordered pairs and produces a map.my
:~
Construct a nullterminated list.
:
%atto
.1e18
:
%femto
.1e15
 ...
:
%exa
.1e18
:
%meter
.1
==
The == boundary closes :~.

 
The   boundary closes our % core.

Flow
 Request an input in the form of a cell
= [frmeas=@tas num=@rs tomeas=@tas]
 Evaluate the next complete hoon expression in light of that following it
=<
 Cast the output as an @rs
^ @rs
 Check whether both frmeas and tomeas are valid measures using the arm check of the core below, and if they are proceed to evaluate the arm “output” using the values resulting from (meters frmeas num) and tomeas, else crash with “Invalid Measures”
?. (check frmeas tomeas)
~("Invalid Measures" !!)
(output (meters frmeas num) tomeas)  Form a core
%
 Create a type called “allowed” that is a list of all valid measurements that we can convert between
+$ allowed ?(%inch %foot %yard %furlong %chain %link %rod %fathom %shackle %cable %nauticalmile %hand %span %cubit %ell %bolt %league %megalithicyard %smoot %barleycorn %poppyseed %atto %femto %pico %nano %micro %milli %centi %deci %meter %deca %hecto %kilo %mega %giga %tera %peta %exa)
 When called at step 4, assist by taking in two @tas values and check to make sure both of them are in the “allowed” type established in step 6.
++ check
= [frmeas=@tas tomeas=@tas]
&(?=(allowed frmeas) ?=(allowed tomeas))
 Convert the “frmeas” units into meters by multiplying the incoming @rs by the conversion factor from the map
++ meters
= [in=@tas value=@rs]
=/ factorone
(~(got by converttomap) in)
(mul:rs value factorone)  Convert the meters from step 8 into the output “tomeas” units by either printing where tomeas is %meters, or dividing the value generated in Step 8 by the conversion factor
++ output
= [in=@rs out=@tas]
?: =(out %meter)
in
(div:rs in (~(got by converttomap) out))  A map of conversion factors  generate this map for comparison in Steps 8 and 9
++ converttomap
^ (map @tas @rs)
% my
:~ : %atto .1e18
: %femto .1e15
: %pico .1e12
: %nano .1e8
:: ...
: %tera .1e12
: %peta .1e15
: %exa .1e18
: %meter .1
==

Readings
Magic 8Ball  https://urbit.org/docs/tutorials/hoon/workbook/eightball/
 NOTE: The magic 8ball employs a generator of a type we’ve not seen, called a %say generator. The first 2 lines and the 4th line are boilerplate, and the 3rd line brings in some “context” of entropy to support randomization. Don’t worry too much about this but try to understand how the rest of the generator works.
2.4 Standard Library: Trees, Sets and Maps  https://urbit.org/docs/tutorials/hoon/treessetsandmaps/
 Read the Maps section  feel free to read the other sections in addition, but the Maps section in particular
Homework
 Add to this generator the ability to convert some other measurement (volume, mass, force, or another of your choosing).
 Add an argument to the cell required by the gate that indicates whether the measurements are distance or your new measurement
 Enforce strictly that the frmeas and tomeas values are either lengths or your type
 Create a new map of conversion values to handle your new measurement conversion method