11Now we've learned enough Hoon to do more interesting things. Let's get
12our planets to talk to each other.
14All we've written up until now are just shell commands that produce a
15value and then disappear. To listen for and receive messages from other
16planets, we'll need an app. Let's look at a very simple one:
18 :: There is no love that is not an echo
20 :::: /hoon/echo/examples/app
32This is a very simple app that does only one thing. If you poke it with
33a value, it prints that value out. To try this out, you have to start
34the app, then you can poke it from the command line with the following
37 ~fintud-macrep:dojo> |start %examples-echo
40 ~fintud-macrep:dojo> :examples-echo 5
43 ~fintud-macrep:dojo> :examples-echo [1 2]
47> There is currently a bug where the `%argument` lines are printed
48> *above* the line you entered, so your output may not look exactly like
49> this. `>=` means that a command was successfully received and
52Most of the app code should be simple enough to guess its function. The
53important part of this code is the definition of `++poke-noun`.
55Once an app starts, it's always running in the background and you
56interact with it by sending it messages. The most straightforward way to
57do that is to poke it from the command line, which we we did with
58`:echo 5` (`:[app-name] [argument(s)]`).
60In this case, `++poke-noun` takes an argument `arg` and prints it out
61with `~&` ([sigpam](../../hoon/twig/sig-hint/pam-dump/)). This is an unusual
62rune that formally "does nothing", but the interpreter detects it and
63printfs the first child, before executing the second as if the first
64didn't exist. This is a slightly hacky way of printing to the console,
65but we'll get to the correct way later on.
67But what does `++poke-noun` produce? Recall that `^-` casts to a type.
68In this case, it's declaring that end result of the function will be of
69type `{(list) _+>.$}`. But what does this mean?
71The phrase to remember is "a list of moves and our state". Urbit is a
72message passing system, so whenever we want to do something that
73interacts with the rest of the system, we send a message. Thus, a move
74is arvo's equivalent of a syscall. The first thing that `++poke-noun`
75produces is a list of messages, called "moves". In this case, we don't
76actually want the system to do anything, so we produce the empty list,
77`~` (in the `[~ +>.$]` line).
79The second thing `++poke-noun` produces is our state. `+>.$` refers to a
80particular address in our subject where our formal app state is stored.
81It'll become clear why this is later on, but for now pretend that `+>.$`
82is a magic invocation that means "app state".
84Let's look at another example (edit your code in
85`/app/examples/echo/hoon` to reflect the code below). Say we want to
86only accept a number, and then print out the square of that number.
95 ~& [%square (mul arg arg)]
99A few things have changed. Firstly, we no longer accept arbitrary nouns
100because we can only square atoms (integers, in this case an unsigned
101one). Thus, our argument is now `arg/@`. Secondly, it's `++poke-atom`
102rather than `++poke-noun`.
107Are there other `++poke`s? Yes. In fact, `noun` and `atom` are just two
108of arbitrarily many "marks". A mark is fundamentally a type definition,
109but accessible at the arvo level. Each mark is defined in the `/mar`
110directory. Some marks have conversion routines to other marks, and some
111have diff, patch, and merge algorithms. None of these are required for a
112mark to exist, however.
114`noun` and `atom` are two predefined marks. In your `/mar` directory,
115there are already many more, and you may add more at will. The type
116associated with `noun` is `*`, and the type associated with `atom` is
119When we poke an app from anywhere, we do so with a mark that searches
120for the corresponding (`++poke-[mark]`) arm. Data constructed on the
121command line is by default marked with `noun`. In this case, the app is
122expecting an atom, so we have to explicitly mark the data with `atom`
123using `&[mark]`. Try the following commands:
125 ~fintud-macrep:dojo> |start %square
127 ~fintud-macrep:dojo> :examples-square 6
128 gall: %square: no poke arm for noun
129 ~fintud-macrep:dojo> :examples-square &atom 6
133> Recall the bug where `%square` may get printed above the input line.
135Marks are powerful, and they're the backbone of Urbit's data pipeline,
136so we'll be getting quite used to them.
140- Write an app that computes fizzbuzz on its input (as in the previous
143Sending a message to another urbit
144==================================
146Let's write our first network message! Here's `/app/examples/pong.hoon`:
150 ++ move {bone term wire *}
157 ^- {(list move) _+>.$}
158 [[[ost %poke /sending [to dap] %atom 'howdy'] ~] +>.$]
162 ^- {(list move) _+>.$}
163 ~& [%receiving (@t arg)]
166 ++ coup |=(* [~ +>.$])
169Run it with these commands:
171 ~fintud-macrep:dojo> |start %examples-pong
173 ~fintud-macrep:dojo> :examples-pong &urbit ~sampel-sipnym
176Replace `~sampel-sipnym` with another urbit. The easiest thing to do is
177to start a moon, a sub-identity of your urbit. If you don't know how to
178start a moon, see [the user admin section](/docs/using/admin/). Don't
179forget to start the `%examples-pong` app on that urbit, too. You should
180see, on the foreign urbit, this output:
184Most of the code should be straightforward. In `++poke-atom`, the only
185new thing is the expression `(@t arg)`, which is the type `@t` being
186called as a function with argument `arg`. As we already know, `@t` is
187the type of "cord" text strings. In Hoon, when types are called as
188functions, they serve as a validator function called a "clam" -- that
189is, a function whose domain is all nouns, and range is the given type
192In simpler terms, if a clam is passed a value of its own type, it
193produces that value. Otherwise, it produces the default value (aka the
194"bunt") of its type. Here we call this `@t` function on the argument.
195This coerces the argument to text, so that we can print it out prettily
196no matter what we're passed.
198The more interesting part is in `++poke-urbit`. The `urbit` mark is an
199urbit identity, and the Hoon type associated with it is `@p` (the "p"
200stands for "phonetic base").
202Recall that in a `++poke` arm we produce "a list of moves and our
203state". Until now, we've left the list of moves empty, since we haven't
204wanted to tell arvo to do anything in particular. Now we want to send a
205message to another urbit. Thus, we produce a list with one element:
207 [ost %poke /sending [to-urbit-address %pong] %atom 'howdy']
211The general form of a move is `[bone term wire *]`.
215`["cause" sys-call/action tack-new-layer-on-cause action-specific information]`
217Let's walk through each of these elements step by step.
221If you look up `++bone` in `hoon.hoon`, you'll see that it's a number
222(`@ud`), and that it's an opaque reference to a duct. `++duct` in
223hoon.hoon is a list of `wire`s, where `++wire` is an alias for `++path`.
224`++path` is a list of `++knot`s, which are ASCII text. Thus, a duct is a
225list of paths, and a bone is an opaque reference to it (in the same way
226that a Unix file descriptor is an opaque reference to a file structure).
227Thus, to truly understand bones, we must understand ducts.
229A duct is stack of causes, again, represented as paths, which are called
230wires. At the bottom of every duct is a unix event, such as a keystroke,
231network packet, file change, or timer event. When arvo is given this
232event, it routes the event to appropriate kernel module for handling.
234Sometimes, the module can immediately handle the event and produce any
235necessary results. For example, when we poked the `++poke-atom` arm
236above, we poked %gall, our application server, which was able to respond
237to our poke directly. When the module cannot service the request itself,
238it sends instructions to another kernel module or application (through the
239%gall module) to do a specified action, and produces the result from
242Furthermore, when one module sends a message to another kernel module or
243application, it also sends along the duct it was given with its new wire
244tacked onto the end. Now the duct has two entries, with the unix even on
245the bottom and the kernel module that handled it next. This process can
246continue indefinitely, adding more and more layers onto the duct. When
247an entity finally produces a result, a layer is popped off the duct, and
248the result is passed all the way back down.
250In effect, a duct is an arvo-level call stack. It's worth noting that
251while in traditional call stacks a function call happens synchronously
252and returns exactly once. In arvo, multiple moves can be sent at once,
253are evaluated asynchronously, and each one may be responded to zero or
256The point to take home is that whatever caused `++poke-urbit` to be
257called is also the root cause for the network message we're trying to
258send. Thus, we say to send the network message along the given bone
261##### Wire ('tack on new layer to duct')
263Of course, we have to push a new layer onto our duct before passing it
264along (or responding to it directly) anywhere. This layer can have any
265data we want in it, but we don't need anything specific here, so we just
266use the wire `/sending` (`/elem1/elem2/elemN` is one syntax used to
267create `++path`s and `++wire`s of N elements). If we were expecting a
268response (which we're not), it would come back along the `/sending`
269wire, meaning that the path `/sending` will be passed back to the
270response handler as an argument. Although it's not required, it's
271generally a good idea to make the wire human-readable for bug-handling
276Each move also has a `term`, composed of lowercase ASCII and/or `-`.
277This `term` has the sign `@tas`. In this case, out `term` is `%poke`,
278which is the name of the particular kind of move we're sending. You can
279always use `%poke` to message an app. Other common names include
280`%warp`, to read from the filesystem; `%wait`, to set a timer; and
281`%them`, to send an http request.
283The move ends with `*` (that is, any noun) since each type of move takes
284different data. In our case, a `%poke` move takes a target (urbit and
285app) and marked data, then pokes the arm of the corresponding mark on
286that app on that urbit with that data. `[to-urbit-address %pong]` is the
287target urbit and app, `%atom` is the `mark`, and`'howdy'` is the data.
289When arvo receives a `%poke` move, it calls the appropriate `++poke`.
290The same mechanism is used for sending messages between apps on the same
291urbit as for sending messages between apps on different urbits.
293> We said earlier that we're not expecting a response. This is not
294> entirely true: the `++coup` is called when we receive acknowledgment
295> that the `++poke` was called. We don't do anything with this
296> information right now, but we could.
300- Extend either of the apps in the first two exercises to accept input
301 over the network in the same way as `pong`.
303- Modify `examples-pong` to print out a message when it receives
306- Write two apps, `even` and `odd`. When you pass an atom to `even`,
307 check whether it's even. If so, divide it by two and recurse;
308 otherwise, poke `odd` with it. When `odd` receives an atom, check
309 whether it's equal to one. If so, terminate, printing "%success".
310 Otherwise, check whether it's odd. If so, multiply it by three, add
311 one, and recurse; otherwise, poke `even` with it. When either app
312 receives a number, print it out along with the name of the app. In
313 the end, you should be able to watch Collatz's conjecture play out
314 between the two apps. Sample output:
317~fintud-macrep:dojo> :even &atom 18
341- Put `even` and `odd` on two separate urbits and pass the messages
342 over the network. Post a link to a working solution in :talk to
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list