10Now we've learned enough hoon to do more interesting things.
11Let's get our planets to talk to each other.
13All we've written up until now are just shell
14commands that produce a value and then disappear. To listen for and receive messages from other planets, we'll need an app. Let's look at a very simple one:
17:: There is no love that is not an echo
19:::: /hoon/echo/examples/app
33This is a very simple app that does only one thing. If you poke
34it with a value, it prints that value out. To try this out, you
35have to start the app, then you can poke it from the command line
36with the following commands:
39~fintud-macrep:dojo> |start %echo
41~fintud-macrep:dojo> :examples-echo 5
44~fintud-macrep:dojo> :examples-echo [1 2]
49<blockquote class="blockquote">
50There is currently a bug where the `%argument` lines are
51printed *above* the line you entered, so your output may not
52look exactly like this.
53`>=` means that a command was successfully received and
57Most of the app code should be simple enough to guess its
58function. The important part of this code is the definition of
61Once an app starts, it's always running in the background and you
62interact with it by sending it messages. The most
63straightforward way to do that is to poke it from the command
64line, which we we did with `:echo 5` (`:[app-name]
67In this case, `++poke-noun` takes an argument `arg` and prints it
68out with `~&`. This is an unusual rune that formally "does
69nothing", but the interpreter detects it and printfs the first
70child, before executing the second as if the first didn't exist.
71This is a slightly hacky way of printing to the console, but
72we'll get to the correct way later on.
74But what does `++poke-noun` produce? Recall that `^-` casts to a
75type. In this case, it's declaring that end result of the
76function will be of type `{(list) _+>.$}`. But what does this
79The phrase to remember is "a list of moves and our state". Urbit
80is a message passing system, so whenever we want to do something that
81interacts with the rest of the system, we send a message. Thus, a
82move is arvo's equivalent of a syscall. The first thing that
83`++poke-noun` produces is a list of messages, called
84"moves". In this case, we don't actually want the system to do
85anything, so we produce the empty list, `~` (in the `[~ +>.$]`
88The second thing `++poke-noun` produces is our state. `+>.$`
89refers to a particular address in our subject where our formal
90app state is stored. It'll become clear why this is later on,
91but for now pretend that `+>.$` is a magic invocation that means
94Let's look at another example (edit your code in `/app/examples/echo/hoon` to reflect the code below). Say we want to only accept a
95number, and then print out the square of that number.
105 ~& [%square (mul arg arg)]
110A few things have changed. Firstly, we no longer accept
111arbitrary nouns because we can only square atoms (integers, in
112this case an unsigned one). Thus, our argument is now `arg/@`.
113Secondly, it's `++poke-atom` rather than `++poke-noun`.
117Are there other `++poke`s? Yes. In fact, `noun` and
118`atom` are just two of arbitrarily many "marks". A mark is
119fundamentally a type definition, but accessible at the arvo
120level. Each mark is defined in the `/mar` directory. Some marks
121have conversion routines to other marks, and some have diff,
122patch, and merge algorithms. None of these are required for a
123mark to exist, however.
125`noun` and `atom` are two of dozens of predefined marks (that will
126already be in your `/mar` directory), and the
127user may add more at will. The type associated with `noun` is
128`*`, and the type associated with `atom` is `@`.
130When we poke an app from anywhere, we do so with a mark that
131searches for the corresponding (`++poke-[mark]`) arm. Data
132constructed on the command line is by default marked with `noun`.
133In this case, the app is expecting an atom, so we have to
134explicitly mark the data with `atom` using `&[mark]`. Try the following
138~fintud-macrep:dojo> |start %square
140~fintud-macrep:dojo> :examples-square 6
141gall: %square: no poke arm for noun
142~fintud-macrep:dojo> :examples-square &atom 6
147> Recall the bug where `%square` may get printed above the input
150Marks are powerful, and they're the backbone of urbit's data
151pipeline, so we'll be getting quite used to them.
155- Write an app that computes fizzbuzz on its input (as in the
158# Sending a message to another ship
160Let's write our first network message! Here's `/app/examples/pong.hoon`:
165 ++ move {bone term wire *}
172 ^- [(list move) _+>.$]
173 [[[ost %poke /sending [to %pong] %atom 'howdy'] ~] +>.$]
177 ^- (list move) _+>.$}
178 ~& [%receiving (@t arg)]
181++ coup |=(* [~ +>.$])
185Run it with these commands:
188~fintud-macrep:dojo> |start %examples-pong
190~fintud-macrep:dojo> :pong &examples-urbit ~sampel-sipnym
194Replace `~sampel-sipnym` with another urbit. The easiest thing to
195do is to start a moon, a sub-identity of your urbit. If you don't know how to start a moon, see [the user admin section](/docs/using/admin/) Don't
196forget to start the `%examples-pong` app on that urbit too. You should
197see, on the foreign urbit, this output:
203Most of the code should be straightforward. In `++poke-atom`,
204the only new thing is the expression `(@t arg)`. As we already
205know, `@t` is the type of "cord" text strings. In Hoon, when types are called as functions, they serve as a validator function (called a "clam" in hoon) -- that is, a function whose domain is all nouns and range is the given type.
207In simpler terms, if a validator function is passed a value that
208falls within its type, that value is produced. Otherwise, it
209produces the default value (aka the "bunt") of its type. Here we call
210this `@t` function on the argument. This coerces the argument
211to text, so that we can print it out prettily no matter what
214The more interesting part is in `++poke-urbit`. The `urbit` mark
215is an urbit identity, and the hoon type associated with it is
216`@p` (the "p" stands for "phonetic base").
218Recall that in a `++poke` arm we produce "a list of moves and our
219state". Until now, we've left the list of moves empty, since we
220haven't wanted to tell arvo to do anything in particular. Now we
221want to send a message to another urbit. Thus, we produce a list
225[ost %poke /sending [to-urbit-address %pong] %atom 'howdy']
230The general form of a move is
235------------------------->app/vane-specific part of move
239`["cause" sys-call/action tack-new-layer-on-cause action-specific
242Let's walk through each of these elements step by step.
246If you look up `++bone` in `hoon.hoon`, you'll see that it's a
247number (`@ud`), and that it's an opaque reference to a duct.
248`++duct` in hoon.hoon is a list of `wire`s, where `++wire` is an
249alias for `++path`. `++path` is a list of `++span`s, which are ASCII
250text. Thus, a duct is a list of paths, and a bone is an opaque
251reference to it (in the same way that a Unix file
252descriptor is an opaque reference to a file structure). Thus, to
253truly understand bones, we must understand ducts.
255A duct is stack of causes, again, represented as paths, which are
256called wires. At the bottom of every duct is a unix event, such
257as a keystroke, network packet, file change, or timer event.
258When arvo is given this event, it routes the event to appropriate
259kernel module for handling.
261Sometimes, the module can immediately handle the event and
262produce any necessary results. For example, when we poked the
263`++poke-atom` arm above, we poked %gall, our application server,
264which was able to respond to our poke directly. When the module
265cannot service the request itself, it sends instructions to another kernel
266module or application (the the %gall module) to do a specified action, and produces the result from that.
268Furthermore, when one module sends a message to another kernel module or application, it also sends along the duct it was given
269with its new wire tacked onto the end. Now the duct has two entries,
270with the unix even on the bottom and the kernel module that
271handled it next. This process can continue indefinitely, adding
272more and more layers onto the duct. When an entity finally
273produces a result, a layer is popped off the duct, and the result
274is passed all the way back down.
276In effect, a duct is an arvo-level call stack. It's worth noting
277that while in traditional call stacks a function call happens
278synchronously and returns exactly once. In arvo, multiple moves
279can be sent at once, are evaluated asynchronously, and each
280one may be responded to zero or more times.
282The point to take home is that whatever caused `++poke-urbit` to be
283called is also the root cause for the network message we're
284trying to send. Thus, we say to send the network message along the given bone
287##### Wire ('tack on new layer to duct')
289Of course, we have to push a new layer onto our duct
290before passing it along (or responding to it directly) anywhere. This
291layer can have any data we want in it, but we don't need anything
292specific here, so we just use the wire `/sending`
293(`/elem1/elem2/elem n` is one syntax used to create `++path`s and
294`++wire`s of n elements). If we were expecting a response (which
295we're not), it would come back along the `/sending` wire, meaning
296that the path `/sending` will be passed back to the response
297handler as an argument. Although it's not required, it's
298generally a good idea to make the wire human-readable for
299bug-handling purposes.
303Looking back at the general form of a move, there is a
304`term`--which is text composed lowercase ascii and `-`, and has
305the sign of `@tas`-- which in this case is `%poke`. This is the
306name of the particular kind of move we're sending. If you think
307of a move as a syscall (which you should), then this `term` is
308the name of the syscall. Common ones include: `%poke`, to
309message an app; `%warp`, to read from the filesystem; `%wait`, to
310set a timer; and `%them`, to send an http request.
312The general form ends with `*` (a noun) since each type of move takes
313different data. In our case, a `%poke` move takes a target
314(urbit and app) and marked data and pokes the arm of the
315corresponding mark on that app on that urbit
316with that data. `[to-urbit-address %pong]` is the target urbit and app,
317`%atom` is the mark`, and `'howdy'` is the data.
319When arvo receives a `%poke` move, it calls the appropriate
320`++poke`. The same mechanism is used for sending messages
321between apps on the same urbit as for sending messages between
322apps on different urbits.
324<blockquote class="blockquote">
325We said earlier that we're not expecting a response. This is
326not entirely true: the `++coup` is called when we receive
327acknowledgment that the `++poke` was called. We don't do
328anything with this information right now, but we could.
333- Extend either of the apps in the first two exercises to accept
334 input over the network in the same way as `pong`.
336- Modify `examples-pong` to print out a message when it receives acknowledgement.
338- Write two apps, `even` and `odd`. When you pass an atom to
339 `even`, check whether it's even. If so, divide it by two and
340 recurse; otherwise, poke `odd` with it. When `odd` recieves
341 an atom, check whether it's equal to one. If so, terminate,
342 printing "%success". Otherwise, check whether it's odd. If
343 so, multiply it by three, add one, and recurse; otherwise, poke
344 `even` with it. When either app receives a number, print it out along with the name of the app. In the end, you should be able to watch Collatz's
345 conjecture play out between the two apps. Sample output:
348~fintud-macrep:dojo> :even &atom 18
372- Put `even` and `odd` on two separate ships and pass the
373 messages over the network.
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