on 01ac191 help sign in
372 lines 13 kB history raw
1---
2next: false
3sort: 3
4title: Network messages
5---
7# Network messages
9Now we've learned enough hoon to do more interesting things.
10Let's get our planets to talk to each other.
12All we've written up until now are just shell
13commands 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:
15```
16:: There is no love that is not an echo
17::
18:::: /hoon/echo/examples/app
19 ::
20/? 314
21!:
22|_ {bowl state/$~}
23++ poke-noun
24 |= arg/*
25 ^- {(list) _+>.$}
26 ~& [%argument arg]
27 [~ +>.$]
28--
29```
32This is a very simple app that does only one thing. If you poke
33it with a value, it prints that value out. To try this out, you
34have to start the app, then you can poke it from the command line
35with the following commands:
37```
38~fintud-macrep:dojo> |start %echo
39>=
40~fintud-macrep:dojo> :examples-echo 5
41[%argument 5]
42>=
43~fintud-macrep:dojo> :examples-echo [1 2]
44[%argument [1 2]]
45>=
46```
48<blockquote class="blockquote">
49There is currently a bug where the `%argument` lines are
50printed *above* the line you entered, so your output may not
51look exactly like this.
52`>=` means that a command was successfully received and
53executed.
54</blockquote>
56Most of the app code should be simple enough to guess its
57function. The important part of this code is the definition of
58`++poke-noun`.
60Once an app starts, it's always running in the background and you
61interact with it by sending it messages. The most
62straightforward way to do that is to poke it from the command
63line, which we we did with `:echo 5` (`:[app-name]
64[argument(s)]).
66In this case, `++poke-noun` takes an argument `arg` and prints it
67out with `~&`. This is an unusual rune that formally "does
68nothing", but the interpreter detects it and printfs the first
69child, before executing the second as if the first didn't exist.
70This is a slightly hacky way of printing to the console, but
71we'll get to the correct way later on.
73But what does `++poke-noun` produce? Recall that `^-` casts to a
74type. In this case, it's declaring that end result of the
75function will be of type `{(list) _+>.$}`. But what does this
76mean?
78The phrase to remember is "a list of moves and our state". Urbit
79is a message passing system, so whenever we want to do something that
80interacts with the rest of the system, we send a message. Thus, a
81move is arvo's equivalent of a syscall. The first thing that
82`++poke-noun` produces is a list of messages, called
83"moves". In this case, we don't actually want the system to do
84anything, so we produce the empty list, `~` (in the `[~ +>.$]`
85line).
87The second thing `++poke-noun` produces is our state. `+>.$`
88refers to a particular address in our subject where our formal
89app state is stored. It'll become clear why this is later on,
90but for now pretend that `+>.$` is a magic invocation that means
91"app state".
93Let'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
94number, and then print out the square of that number.
96```
97/? 314
98!:
99|_ {bowl state=~}
100::
101++ poke-atom
102 |= arg/@
103 ^- {(list) _+>.$}
104 ~& [%square (mul arg arg)]
105 [~ +>.$]
106--
107```
109A few things have changed. Firstly, we no longer accept
110arbitrary nouns because we can only square atoms (integers, in
111this case an unsigned one). Thus, our argument is now `arg/@`.
112Secondly, it's `++poke-atom` rather than `++poke-noun`.
114# Intro to marks
116Are there other `++poke`s? Yes. In fact, `noun` and
117`atom` are just two of arbitrarily many "marks". A mark is
118fundamentally a type definition, but accessible at the arvo
119level. Each mark is defined in the `/mar` directory. Some marks
120have conversion routines to other marks, and some have diff,
121patch, and merge algorithms. None of these are required for a
122mark to exist, however.
124`noun` and `atom` are two of dozens of predefined marks (that will
125already be in your `/mar` directory), and the
126user may add more at will. The type associated with `noun` is
127`*`, and the type associated with `atom` is `@`.
129When we poke an app from anywhere, we do so with a mark that
130searches for the corresponding (`++poke-[mark]`) arm. Data
131constructed on the command line is by default marked with `noun`.
132In this case, the app is expecting an atom, so we have to
133explicitly mark the data with `atom` using `&[mark]`. Try the following
134commands:
136```
137~fintud-macrep:dojo> |start %square
138>=
139~fintud-macrep:dojo> :examples-square 6
140gall: %square: no poke arm for noun
141~fintud-macrep:dojo> :examples-square &atom 6
142[%square 36]
143>=
144```
146> Recall the bug where `%square` may get printed above the input
147> line.
149Marks are powerful, and they're the backbone of urbit's data
150pipeline, so we'll be getting quite used to them.
152**Exercise**:
154- Write an app that computes fizzbuzz on its input (as in the
155 previous section).
157# Sending a message to another ship
159Let's write our first network message! Here's `/app/examples/pong.hoon`:
161```
162/? 314
163|%
164 ++ move {bone term wire *}
165--
166!:
167|_ {bowl state=$~}
168::
169++ poke-urbit
170 |= to/@p
171 ^- [(list move) _+>.$]
172 [[[ost %poke /sending [to %pong] %atom 'howdy'] ~] +>.$]
173::
174++ poke-atom
175 |= arg/@
176 ^- (list move) _+>.$}
177 ~& [%receiving (@t arg)]
178 [~ +>.$]
179::
180++ coup |=(* [~ +>.$])
181--
182```
184Run it with these commands:
186```
187~fintud-macrep:dojo> |start %examples-pong
188>=
189~fintud-macrep:dojo> :pong &examples-urbit ~sampel-sipnym
190>=
191```
193Replace `~sampel-sipnym` with another urbit. The easiest thing to
194do 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
195forget to start the `%examples-pong` app on that urbit too. You should
196see, on the foreign urbit, this output:
198```
199[%receiving 'howdy']
200```
202Most of the code should be straightforward. In `++poke-atom`,
203the only new thing is the expression `(@t arg)`. As we already
204know, `@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.
206In simpler terms, if a validator function is passed a value that
207falls within its type, that value is produced. Otherwise, it
208produces the default value (aka the "bunt") of its type. Here we call
209this `@t` function on the argument. This coerces the argument
210to text, so that we can print it out prettily no matter what
211we're passed.
213The more interesting part is in `++poke-urbit`. The `urbit` mark
214is an urbit identity, and the hoon type associated with it is
215`@p` (the "p" stands for "phonetic base").
217Recall that in a `++poke` arm we produce "a list of moves and our
218state". Until now, we've left the list of moves empty, since we
219haven't wanted to tell arvo to do anything in particular. Now we
220want to send a message to another urbit. Thus, we produce a list
221with one element:
223```
224[ost %poke /sending [to-urbit-address %pong] %atom 'howdy']
225```
227### Moves
229The general form of a move is
231-arvo part---->
232`[bone term wire *]`
234------------------------->app/vane-specific part of move
236Or, in pseudo-code:
238`["cause" sys-call/action tack-new-layer-on-cause action-specific
239information]`
241Let's walk through each of these elements step by step.
243#### Bones ("cause")
245If you look up `++bone` in `hoon.hoon`, you'll see that it's a
246number (`@ud`), and that it's an opaque reference to a duct.
247`++duct` in hoon.hoon is a list of `wire`s, where `++wire` is an
248alias for `++path`. `++path` is a list of `++span`s, which are ASCII
249text. Thus, a duct is a list of paths, and a bone is an opaque
250reference to it (in the same way that a Unix file
251descriptor is an opaque reference to a file structure). Thus, to
252truly understand bones, we must understand ducts.
254A duct is stack of causes, again, represented as paths, which are
255called wires. At the bottom of every duct is a unix event, such
256as a keystroke, network packet, file change, or timer event.
257When arvo is given this event, it routes the event to appropriate
258kernel module for handling.
260Sometimes, the module can immediately handle the event and
261produce any necessary results. For example, when we poked the
262`++poke-atom` arm above, we poked %gall, our application server,
263which was able to respond to our poke directly. When the module
264cannot service the request itself, it sends instructions to another kernel
265module or application (the the %gall module) to do a specified action, and produces the result from that.
267Furthermore, when one module sends a message to another kernel module or application, it also sends along the duct it was given
268with its new wire tacked onto the end. Now the duct has two entries,
269with the unix even on the bottom and the kernel module that
270handled it next. This process can continue indefinitely, adding
271more and more layers onto the duct. When an entity finally
272produces a result, a layer is popped off the duct, and the result
273is passed all the way back down.
275In effect, a duct is an arvo-level call stack. It's worth noting
276that while in traditional call stacks a function call happens
277synchronously and returns exactly once. In arvo, multiple moves
278can be sent at once, are evaluated asynchronously, and each
279one may be responded to zero or more times.
281The point to take home is that whatever caused `++poke-urbit` to be
282called is also the root cause for the network message we're
283trying to send. Thus, we say to send the network message along the given bone
284`ost`.
286##### Wire ('tack on new layer to duct')
288Of course, we have to push a new layer onto our duct
289before passing it along (or responding to it directly) anywhere. This
290layer can have any data we want in it, but we don't need anything
291specific here, so we just use the wire `/sending`
292(`/elem1/elem2/elem n` is one syntax used to create `++path`s and
293`++wire`s of n elements). If we were expecting a response (which
294we're not), it would come back along the `/sending` wire, meaning
295that the path `/sending` will be passed back to the response
296handler as an argument. Although it's not required, it's
297generally a good idea to make the wire human-readable for
298bug-handling purposes.
300##### Term (sys-call)
302Looking back at the general form of a move, there is a
303`term`--which is text composed lowercase ascii and `-`, and has
304the sign of `@tas`-- which in this case is `%poke`. This is the
305name of the particular kind of move we're sending. If you think
306of a move as a syscall (which you should), then this `term` is
307the name of the syscall. Common ones include: `%poke`, to
308message an app; `%warp`, to read from the filesystem; `%wait`, to
309set a timer; and `%them`, to send an http request.
311The general form ends with `*` (a noun) since each type of move takes
312different data. In our case, a `%poke` move takes a target
313(urbit and app) and marked data and pokes the arm of the
314corresponding mark on that app on that urbit
315with that data. `[to-urbit-address %pong]` is the target urbit and app,
316`%atom` is the mark`, and `'howdy'` is the data.
318When arvo receives a `%poke` move, it calls the appropriate
319`++poke`. The same mechanism is used for sending messages
320between apps on the same urbit as for sending messages between
321apps on different urbits.
323<blockquote class="blockquote">
324We said earlier that we're not expecting a response. This is
325not entirely true: the `++coup` is called when we receive
326acknowledgment that the `++poke` was called. We don't do
327anything with this information right now, but we could.
328</blockquote>
330**Exercises**:
332- Extend either of the apps in the first two exercises to accept
333 input over the network in the same way as `pong`.
335- Modify `examples-pong` to print out a message when it receives acknowledgement.
337- Write two apps, `even` and `odd`. When you pass an atom to
338 `even`, check whether it's even. If so, divide it by two and
339 recurse; otherwise, poke `odd` with it. When `odd` recieves
340 an atom, check whether it's equal to one. If so, terminate,
341 printing "%success". Otherwise, check whether it's odd. If
342 so, multiply it by three, add one, and recurse; otherwise, poke
343 `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
344 conjecture play out between the two apps. Sample output:
346```
347~fintud-macrep:dojo> :even &atom 18
348[%even 18]
349[%odd 9]
350[%even 28]
351[%even 14]
352[%odd 7]
353[%even 22]
354[%odd 11]
355[%even 34]
356[%odd 17]
357[%even 52]
358[%even 26]
359[%odd 13]
360[%even 40]
361[%even 20]
362[%even 10]
363[%odd 5]
364[%even 16]
365[%even 8]
366[%even 4]
367[%even 2]
368%success
369```
371- Put `even` and `odd` on two separate ships and pass the
372 messages over the network.