25 KiB
%ford
Guide
basic hoon
and talking to the web
%ford
is the arvo vane that handles asset management and publishing.
We use %ford
to compose our files when programming, and also to bundle
files together for the web.
This guide assumes that you have installed and started your urbit.
Assuming you cloned the repo into /$URB_DIR
you should be able to find
the unix mirror of your ship's filesystem in
/$URB_DIR/$PIER/$SHIP-NAME
. All of the filesystem paths in this guide
are relative to that Unix directory.
Also, we refer to http://ship-name.urbit.org/
in our URLs, but you can
also use http://localhost:8080/
if you prefer to just talk to your
machine directly.
1. Let's publish a webpage --------------------------
In
/main/pub/fab/guide/exercise/1/hymn.hook
Put
;html
;head
;meta(charset "utf-8");
;title: Exercise 1
==
;body
;div
;h1: Exercise 1 — Simple HTML
;p: As you may notice, urbit has no problem talking to the web.
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/1/
What did you just do?
The code you just wrote is urbit's native programming langauge, hoon.
Generating HTML with hoon is similar to writing jade or other
similar HTML shorthand. In hoon, this shorthand is called ++sail
and it's a native part of the hoon language.
In ++sail
node-names are prefixed with a ;
and closed with a ==
.
Nodes that have text content and are only on one line use a :
, such as
our ;h1:
above, and are closed implicitly with a new line. Nodes with
no content are closed with another ;
, such as ;br;
.
You can find more information about ++sail
in our rune library
documentation.
2. Let's do some programming on the page. -----------------------------------------
In
/pub/fab/guide/exercise/2/hymn.hook
Put
;html
;head
;meta(charset "utf-8");
;title: Exercise 2
==
;body
;div
;h1: Exercise 2 — Call a function
;p: Although it may be obvious, 2+2={<(add 2 2)>}
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/2/
What's going on there?
Clearly, the code (add 2 2)
is generating 4
, and it's not too hard
to see why. add
is one of the library functions that are a part of
hoon.hoon
. Try replacing (add 2 2)
with (sub 2 (add 2 2))
. You can
find documentation for the full hoon library in the library
reference.
Since the product of (add 2 2)
is a number, we need a few extra things
to have it print properly. In ++sail
we use {
and }
to do string
interpolation. Everything after the :
is expected to be a string, so
we need to tell the parser when we start writing code. The <
and >
inside our {
}
are converting our result 4
, a number, to the
string "4"
. hoon is a strongly typed language kind of like haskell, so
we need to explicitly convert our types.
3. Let's assign some variables. -------------------------------
In
/pub/fab/guide/exercise/3/hymn.hook
Put
=+ ^= a 1
=+ b=2
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: Exercise 3
==
;body
;div
;h1: Exercise 3 — Assignment
;p: a={<a>}
;p: b={<b>}
;p: a+b={<(add a b)>}
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/3/
How does that work?
The first thing you should notice in this example is the =+
at the top
of our file. =+
is a rune. hoon is a programming with no reserved
words. We don't use if
this
or function
at all. Instead, runes
have their own pronunciation. =+
is pronounced 'tislus'. You can find
the table of pronunciation here. In hoon you construct your
programs using runes, which are two character ascii pairs. You can see
the whole set of runes in the rune index.
=+
pushes an expression on to our subject. The subject in hoon is
similar to this
in other languages. hoon being a functional language
if we want something to be available further on in our computation we
need to attach it to the subject first.
The second thing you should notice is the ^- manx
. Here the rune
[^-]()
is used to cast our product to a ++manx, which you can
think of like the hoon MIME type for XML. Using a ^-
is not required,
but helps us produce more informative error messages when we generate a
type error or mismatch.
Looking at the rendered page it's clear that we're assigning a
to be
1
and b
to be 2
. Looking at the code, however, you can see that
we're doing this in two different ways. Runes in hoon can have irregular
forms, and ^=
is one of them. The first two lines of our example are
doing the same thing, where a=2
is simply the irregular form of
^= a 2
. You can see the full list of irregular forms here.
Looking at the simple computation on the page, you can try changing the
values of a
and b
to any integers to produce similar results.
4. Let's build our own computation ----------------------------------
In
/pub/fab/guide/exercise/4/hymn.hook
Put
|%
++ start 1
++ end 10
++ length
|= [s=@ud e=@ud]
(sub e s)
--
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: Exercise 4
==
;body
;div
;h1: Exercise 4 — Cores
;p: We'll be starting at {<start>}
;p: And ending at {<end>}
;p: Looks like a length of {<(length start end)>}
==
==
==
Try it
(and be sure to put two spaces between ++
and arm names)
http://ship-name.urbit.org/main/pub/fab/guide/exercise/4/
What's happening?
As you can see from the output, we have written a little function that
takes two numbers, s
and e
and returns their difference. The first
thing to notice about our code is the first rune. |%
is a core
rune.
You can think of cores like functions or objects in other languages.
|%
runes contain an arbitrary number of arms, denoted with either ++
or +-
and closed with --
.
Each arm has a value, either static data (in the case of ++start
and
++end
) or a gate (in the case of ++length
). A gate is a kind of
core. Gates only have one arm and are quite similar to a function in
other languages. We use |=
to construct our gate. Runes in hoon are
generally categorized by their first character. |
indicates a rune
having to do with cores. You can find all of the |
runes in the rune
library.
Our ++length
gate takes two arguments, s
and e
. In hoon we
call the data passed in to a gate the 'sample'. Every |=
has two
parts: the sample type and the computation, also known as a tile
and a
twig
. Casually, [s=@ud e=@ud]
means that the gate takes two
arguments, labelled going forward as s
and e
, and required to both
be @ud
or unsigned decimal. Our computation, (sub e s)
simply
computes the difference between e
and s
.
@ud
is an odor. Odors aren't quite types, but they're similar. You'll
learn the difference by example as we progress, and you can always refer
to the odor index.
You probably also noticed our indentation. In general hoon has both tall and wide forms. In tall form, hoon uses two spaces for indentation and is back-stepped so nested code doesn't drift away toward the right margin. In wide form we use parenthesis just like almost everyone else.
5. --
In
/pub/fab/guide/exercise/5/hymn.hook
Put
|%
++ dist ,[start=@ud end=@ud]
++ length
|= [d=dist]
(sub end.d start.d)
--
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: Exercise 5
==
;body
;div
;h1: Exercise 5 — Cores
;p: How long does it take to get from 2 to 20? {<(length 2 20)>}
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/5/
What's the difference?
Clearly we're producing the same result as before, but we're doing it in a different way.
The first line in our gate, ++length
always specifies the sample tile.
As you can see here, our sample tile is actually a ++dist
, the body of
which, ,[start=@ud end=@ud]
looks very similar to our previous example
in that it accepts two @ud
. The important difference is that ++dist
is now defined in a way that can be re-used. hoon is a strongly typed
language, but it encourages the creation of your own types using what we
call tiles.
At a high level you can think of hoon as being composed of two things, tiles and twigs. Twigs are the actual AST structures that get consumed by the compiler. Tiles reduce to twigs and provide major affordances for the programmer. If you're interested in learning about tiles more deeply you can find an in-depth explanation in the tile overview.
It should suffice, for now, to say that we create tiles in the same way
that you would think of creating type definitions in another language.
The primary way we do this is with $
runes you can find more about
them in the $
section of the rune library.
In this specific example we are using the $,
tile rune in its
irregular form, ,
. ,
generates a validator from the given
expression. In effect, ++dist
uses the type system to only produce
cells that appear in the form [start=@ud end=@ud]
. When we use it in
our ++length
gate we assert that our input must be validated by
++dist
. To test that out, you can try passing something that isn't an
unsigned decimal (or @ud
) to ++length
. Try replacing (length 2 20)
with (length 2 'a')
. As we continue you'll see how this pattern can be
quite useful.
One other thing to point out which may be immediately confusing coming
from other languages is the order of addressing start
and end
. We
call these labels faces, and we address them in the opposite order than
you're usually familiar with. We still separate our addressing with .
,
but do it from the inside out. Given a tuple such as
[a=1 b=[c=[d=2 e=3]]]
we can address the value of e
with e.c.b
.
You can read more about how faces work in the commentary on ++type
here.
6. --
In
/pub/fab/guide/exercise/6/hymn.hook
Put
|%
++ fib
|= x=@
?: (lte x 2)
1
(add $(x (dec x)) $(x (sub x 2)))
--
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: Exercise 6
==
;body
;div
;h1: Exercise 6 — Loops
;p: {<(fib 1)>}, {<(fib 2)>}, {<(fib 3)>}, {<(fib 4)>}
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/6/
What is that doing?
We're printing a few members of the fibonacci sequence by
calling our arm ++fib
with a few values. The fibonacci sequence is a
fairly straight forward algorithm: F(n-1) + F(n-2)
where F(1) = 1
and F(2) = 1
. As is obvious from the formula, generating the fibonacci
value at any number requires us to recurse, or loop. Let's walk through
the code.
Our example here should look similar to the previous one. We build a
core with |%
and add the arm ++fib
. ++fib
is a gate which takes
one argument x
, an atom. Using ?:
we test if x
is less
than 2
with the library function lte
to handle our seed
values of F(1) = 1
and F(2) = 1
. ?:
is a member of the ?
runes, related to true / false values, or loobeans. In hoon true
and false are 0
and 1
respectively and take the odor @f
. We tend
to say 'yes' and 'no' instead of 'true' and 'false' to keep track of the
switch. Our built-in function lte
produces a loobean, so we evaluate
our first line if true, and second if false.
If x
is not less than 2
we compute F(n-1) + F(n-2)
by using $
.
We mentioned previously that a gate is a special kind of core with only
one arm, called $
. Here we're using $
to mimic the behavior of a
loop. You can read the expression $(x (dec x))
as 'call the gate again
with x
replaced by (dec x)
. For more on how this works, check out
the documentation of %=
and %-
. With that in mind it
should be clear how the last line of ++fib
produces the member of the
sequence at a given value x
.
7. --
In
/pub/fab/guide/exercise/7/hymn.hook
Put
::
::
:::: /hook/hymn/7/exercise/guide/fab/pub/
::
/? 314
/= gas /$ fuel
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: %ford Example 1
==
;body
;div
;h1: %ford Example 1 — Page Variables
;div.who: {<(~(get ju aut.ced.gas) 0)>}
;div.where: {(spud s.bem.gas)} rev {(scow %ud p.r.bem.gas)}
;code
;pre: {<gas>}
==
==
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/7/
http://ship-name.urbit.org/gin/del/main/pub/fab/guide/exercise/7/
Here we're putting our publishing framework, %ford
to work a little
bit. We're printing out some of the parameters our page is passed: who
is looking at it, where it is and what revision our desk is on. We have
also thrown in all our FCGI parameters in a codeblock for reference.
To do this we have introduced some new runes, /?
, /=
and /$
. We
tend to call runes with a leading /
"%ford
runes" since they are
used by the %ford
vane for resource loading and composition. By
convention, we indent four spaces after them to make them more obvious.
They belong at the top of the file.
/?
simply checks for compatibility. In this case the line means 'need
urbit 314 or below', or in hoon: (lte zuse 314)
. 314
is the number
in the kelvin versioning system, which you can read about here.
/=
is similar to the combination of =+ =^
, or assignment. /$
calls a parsing function, which we specify as ++fuel
with the
++beam
and ++path
of our current file.
/= gas /$ fuel
is a common way to open your page, since the
product of ++fuel
is useful when writing pages to the web. The use of
++fuel
is not enforced — you can also write your own parser.
Our page is made up of two generated parts: who requested the page, the
location of the page and its revision. Both are parsed out of the gas
variable using some straightforward library functions, ++ju
,
++spud
and ++scow
. You can follow those links to the
library reference to learn more about them. You'll also notice our
addressing moving in the opposite direction as you may be used to.
aut.ced.gas
pulls aut
from inside ced
from inside gas
.
Inside of the ;code
tag we also print (for our own reference) the
entire gas
, so you can take a look at the contents. This can be a
helpful trick when debugging. To fully understand what gets put in
gas
, we can take a look at ++fuel
and note that it produces a
++epic
, which also contains a ++cred
. You can follow
those links to learn more about them.
When we try changing the url from main
to gin/del/main
we're
using some of the access methods from %eyre
(the urbit webserver) to
pretend to be the urbit ~del
. You can find documentation on those
access methods in the %eyre
commentary, here.
Path and identity are useful, but there are some other parameters worth checking out as well.
8. --
In
/pub/fab/guide/exercise/8/hymn.hook
Put
::
::
:::: /hook/hymn/8/exercise/guide/fab/pub/
::
/? 314
/= gas /$ fuel
::
^- manx
;html
;head
;title: %ford Example 2
==
;body
;div: Do you have a code?
;div: ?code={<(fall (~(get by qix.gas) %code) '')>}
==
==
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/8/
http://ship-name.urbit.org/main/pub/fab/guide/exercise/8/?code=yes-i-do
This is a simple example, showing off another use of
/= gas /$ fuel
. In this case we're just pulling out the value of
the code
query string parameter. You should be able to change that
value to any url-safe string and see it appear on the page.
We're using a few simple library functions to actually pull the value
out, ++fall
and get:by
. Query string parameters are
stored in qix.gas
as a ++map
, one of the main container constructs
used in hoon. We'll encounter a lot of maps along the way, and you can
learn more about them in the map section of the library doc.
9. --
In
/pub/fab/guide/exercise/9/hymn.hook
Put
::
::
:::: /hook/hymn/exercise/9/guide/fab/pub/
::
/? 314
::
// /%%/lib
^- manx
;html
;head
;title: %ford Example 3
==
;body
;h1: %ford Example 3
;p: {<(fib 1)>}, {<(fib 2)>}, {<(fib 3)>}, {<(fib 4)>}
==
==
And in
/pub/fab/guide/exercise/9/lib.hoon
Put
|%
++ fib
|= x=@
?: (lte x 2)
1
(add $(x (dec x)) $(x (sub x 2)))
--
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/9/
How are they getting combined?
Clearly this looks a lot like our previous example using ++fib
, only
now we're using two separate files. The majority of this code should be
familiar to you from example 6, so let's focus on a single line,
// /%%/lib
.
//
is a %ford
rune that loads a resource from a given path. //
is
used as a way to organize project code into separate files, not for
loading libraries and structures. We'll show some examples of how urbit
handles those things shortly. You can think of //
as a kind of
include
or require
. //
takes a beam
, an absolute global path in
%clay
— the global urbit filesystem.
In %clay
we use %
to navigate relative paths. %
is sort of like
.
when moving around the unix file system. Although we put our code in
the unix path /pub/fab/guide/exercise/9/hymn.hook
the file extension
is just a hint to the system. /%/
(the equivalent of a unix ./
)
actually resolves to /pub/fab/guide/exercise/9/hymn
.
We commonly use two kinds of extensions, .hoon
for source files and
.hook
for files that generate something else. Since our hymn file is
generating html, it's a .hook
, and our source file is just a .hoon
.
In order to find our file one level up we need two %%
to get to
/pub/fab/guide/exercise/9/
. Adding lib
resolve to our neighboring
file. You can read more about how %clay
paths are parsed in the
%clay
overview. It's also pretty easy to try them out using
/=main=
, /=try
, /try/a/b/c/d
, etc.
and :ls
in your %arvo
terminal.
10. ---
In
/pub/fab/guide/exercise/10/hymn.hook
Put
::
::
:::: /hook/hymn/10/exercise/guide/fab/pub/
::
/? 314
/= gas /$ fuel
// /%%/lib
::
=+ ^= arg
%+ slav
%ud
%+ fall
%- ~(get by qix.gas) %number
'0'
::
^- manx
;html
;head
;title: %ford Example 4
==
;body
;div: {<(fib arg)>}
==
==
And in
/pub/fab/guide/exercise/10/lib.hoon
Put
|%
++ fib
|= x=@
?: (lte x 2)
1
(add $(x (dec x)) $(x (sub x 2)))
--
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/10/
http://ship-name.urbit.org/main/pub/fab/guide/exercise/10/?number=7
http://ship-name.urbit.org/main/pub/fab/guide/exercise/10/?number=12
As you can see by changing the URL, we're now passing our query string
parameter to our ++fib
script and printing the output. It's common to
pull some data out of the URL and do something with it, but we don't
have any type information about our query string parameters and hoon is
a strongly typed languge. As you can see, we're calling ++fib
with a
parameter arg
. Let's look closely at how we generate arg
.
The first part of our assignment should look familiar from previous
example, =+ ^= arg
puts the face arg
on the product of our
remaining computation. In short, you can read the code that produces
arg
as (slav %ud (fall (~(get by qix.gas) %number) '0'))
. We use %
runes to write this in tall form. %
runes are used for calling gates
or evaluating changes. %+
'slams' or calls a gate with two arguments,
and %-
'slams' or calls a gate with one argument. As usual, you can
find more about the %
runes in the %
section of the rune
library.
To get a value out of our map of query string parameters qix.gas
we
use a method from the maps library that produces a ++unit
.
++unit
s are a common type in hoon used for optional values. A
++unit
is either ~
or [~ p=value]
. Since we need to
specify a value for (fib arg)
even when someone doesn't enter the
query string we use ++fall
, which produces either the value of
the unit, or the second argument if the ++unit
is null. Since our
qix.gas
has string values in it we specify a string in our second
argument, '0'
. As an aside, '0'
is different from "0"
in hoon. You
can read about the difference in ++cord
and ++tape
.
Our outermost call, to ++slav
, casts our string to a @ud
—
which is the type expected by ++fib
. ++slav
takes the name of an
odor and a value, and tries to cast the value to that odor.
11. ---
In
/pub/fab/guide/exercise/11/hymn.hook
Put
/= posts /: /%%/lib
/; |= a=(list (pair ,@ ,manx))
%+ turn
a
|= [* b=manx]
b
/@
/psal/
::
^- manx
;html
;head
;meta(charset "utf-8");
;title: %ford Example 11
==
;body
;h1: Ford example — Loading Resources by Number
;* posts
==
==
In
/pub/fab/guide/exercise/11/lib/1.md
Put
#1
This is my first post.
In
/pub/fab/guide/exercise/11/lib/2.md
Put
#2
This is my second post.
Try it
http://ship-name.urbit.org/main/pub/fab/guide/exercise/11/
http://ship-name.urbit.org/main/pub/fab/guide/exercise/11/lib/1/
http://ship-name.urbit.org/main/pub/fab/guide/exercise/11/lib/2/
Experiment with it
Try adding other .md
files with numeric file names (such as 3.md
) to
the 11/lib/
directory to get a feel for what's going on.
What's happening?
As you can see, we're loading the markdown files in 11/lib
and putting
them in to our page. Let's dive into the code.
We're using /=
to assign the posts
face. /:
sets the ++beam
for
the computation below it. You can think of it sort of like setting an
environment variable. Everything below uses our ++beam
/%%/lib
.
If we take the next few lines and write them as pseudo code in wide form
they might look something like this, (/; [gate] (/@ /psal/))
. That
being the case, let's start at the bottom and move upwards since that's
how our data flows. In depth documentation on individual ++horn
runes
can be found in the horn section of the rune library.
/psal/
loads our psal
mark. Marks are like content types, and we
keep them in /main/mar/
. You can open /main/mar/psal/door.hook
to
see that we specify the ways in which a particular mark can be converted
to produced well typed output. The general form of this is /mark/
where mark
exists in the /main/mar/
directory. A psal
is a partial
hymn
, where hymn
is the hoon structure for html
.
/@
loads a list of files in numerical order from the previously
specified ++beam
using our mark, psal
. /@
has a few close
relatives. /&
, for example, reads files by @da
or absolute date. You
can see the rest in the horn section of the library.
/;
takes the output from /@
and /psal/
and passes it to a twig. In
this case, a gate. Our /@
actually produces a ++list
of
pairs of [@ manx]
where the @
is the filename, and the manx
is the
converted contents. We use ++turn
, one of our ++list
operator functions, to iterate through the list and produce only a list
of ++manx
. This is the output assigned to posts
.
Then, further down, we use ;*
to write the list to the page.
12. ---
Look in
/pub/fab/guide/hymn.hook
/pub/fab/guide/main.css
/pub/fab/guide/main.js
Try it
http://ship-name.urbit.org/main/pub/fab/guide/
Bring it all together
It turns out it's pretty easy to pull our examples together into a single blog-like page using what we just covered. We include some css to make things a bit prettier, and this should give you a good jumping off point for experimenting with your own publishing code.