on ecfde25 help sign in
384 lines 13 kB history raw
1---
2next: false
3sort: 2
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. We need an
14actual app to listen for messages from another planet. Let's
15take a look at a very basic app that does this.
17```
18:: There is no love that is not an echo
19::
20:::: /hoon/echo/ape
21 ::
22/? 314
23!:
24|_ [bowl state=~]
25++ poke-noun
26 |= arg=*
27 ^- [(list) _+>.$]
28 ~& [%argument arg]
29 [~ +>.$]
30--
31```
34This is a very simple app that does only one thing. If you poke
35it with a value, it prints that value out. To try this out, you
36have to start the app, then you can poke it from the command line
37with the following commands:
39```
40~fintud-macrep:dojo> |start %echo
41>=
42~fintud-macrep:dojo> :echo 5
43[%argument 5]
44>=
45~fintud-macrep:dojo> :echo [1 2]
46[%argument [1 2]]
47>=
48```
50<blockquote class="blockquote">
51There is currently a bug where the `%argument` lines are
52printed *above* the line you entered, so your output may not
53look exactly like this.
54`>=` means that a command was successfully received and
55executed.
56</blockquote>
58Most of the app code should be simple enough to guess its
59function. The important part of this code is the definition of
60`++poke-noun`.
62Once an app starts, it's always running in the background and you
63interact with it by sending it messages. The most
64straightforward way to do that is to poke it from the command
65line, which we we did with `:echo 5` (`:[app-name]
66[argument(s)]).
68In this case, `++poke-noun` takes an argument `arg` and prints it
69out with `~&`. This is an unusual rune that formally "does
70nothing", but the interpreter detects it and printfs the first
71child, before executing the second as if the first didn't exist.
72This is a slightly hacky way of printing to the console, but
73we'll get to the correct way later on.
75But what does `++poke-noun` produce? Recall that `^-` casts to a
76type. In this case, it's declaring that end result of the
77function will be of type `[(list) _+>.$]`. But what does this
78mean?
80The phrase to remember is "a list of moves and our state". Urbit
81is a message passing system XX should we mention event
82driven operating system XX, so whenever we want to do something that
83interacts with the rest of the system we send a message. Thus, a
84move is arvo's equivalent of a syscall. The first thing that
85`++poke-noun` produces is a list of messages, called
86"moves". In this case, we don't actually want the system to do
87anything, so we produce the empty list, `~` (in the `[~ +>.$]`
88line).
90The second thing `++poke-noun` produces is our state. `+>.$`
91refers to a particular address in our subject where our formal
92app state is stored. It'll become clear why this is later on,
93but for now pretend that `+>.$` is a magic invocation that means
94"app state".
96Let's look at another example. Say we want to only accept a
97number, and then print out the square of that number.
99```
100/? 314
101!:
102|_ [bowl state=~]
103::
104++ poke-atom
105 |= arg=@
106 ^- [(list) _+>.$]
107 ~& [%square (mul arg arg)]
108 [~ +>.$]
109--
110```
112A few things have changed. Firstly, we no longer accept
113arbitrary nouns because we can only square atoms (integers, in
114this case an unsigned one). Thus, our argument is now `arg=@`.
115Secondly, 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
135commands:
137```
138~fintud-macrep:dojo> |start %square
139>=
140~fintud-macrep:dojo> :square 6
141gall: %square: no poke arm for noun
142~fintud-macrep:dojo> :square &atom 6
143[%square 36]
144>=
145```
147> Recall the bug where `%square` may get printed above the input
148> line.
150Marks are powerful, and they're the backbone of urbit's data
151pipeline, so we'll be getting quite used to them.
153**Exercises**:
155- Write an app that computes fizzbuzz on its input (as in the
156 previous section).
158- One way of representing strings is with double quoted strings
159 called "tapes". The hoon type is `++tape`, and there is a
160 corresponding mark with the same name. Write an app that
161 accepts a tape and prints out `(flop argument)`, where
162 `argument` is the input. What does this do?
165Let's write our first network message! Here's `/ape/pong.hoon`:
167```
168/? 314
169|%
170 ++ move ,[bone term wire *]
171--
172!:
173|_ [bowl state=~]
174::
175++ poke-urbit
176 |= to=@p
177 ^- [(list move) _+>.$]
178 [[[ost %poke /sending [to %pong] %atom 'howdy'] ~] +>.$]
179::
180++ poke-atom
181 |= arg=@
182 ^- [(list move) _+>.$]
183 ~& [%receiving (,@t arg)]
184 [~ +>.$]
185::
186++ coup |=(* [~ +>.$])
187--
188```
190Run it with these commands:
192```
193~fintud-macrep:dojo> |start %pong
194>=
195~fintud-macrep:dojo> :pong &urbit ~sampel-sipnym
196>=
197```
199Replace `~sampel-sipnym` with another urbit. The easiest thing to
200do is to start a [moon](), a sub-identity of your urbit. Don't
201forget to start the `%pong` app on that urbit too. You should
202see, on the foreign urbit, this output:
204```
205[%receiving 'howdy']
206```
208Most of the code should be straightforward. In `++poke-atom`,
209the only new thing is the expression `(,@t arg)`. As we already
210know, `@t` is the type of "cord" text strings. `,` is the
211irregular short form of `$,`an operator that turns a type into a
212validator function (called a "clam" in hoon) -- that is, a
213function whose domain is all nouns and range is the given type.
214In simpler terms, if a validator function is passed a value that
215falls within its type, that value is produced. Otherwise, it
216produces the default value (aka the "bunt") of its type. Here we call
217this `,@t` function on the argument. This coerces the argument
218to text, so that we can print it out prettily no matter what
219we're passed.
221The more interesting part is in `++poke-urbit`. The `urbit` mark
222is an urbit identity, and the hoon type associated with it is
223`@p` (the "p" stands for "phonetic base").
225Recall that in a `++poke` arm we produce "a list of moves and our
226state". Until now, we've left the list of moves empty, since we
227haven't wanted to tell arvo to do anything in particular. Now we
228want to send a message to another urbit. Thus, we produce a list
229with one element:
231```
232[ost %poke /sending [to-urbit-address %pong] %atom 'howdy']
233```
235### Moves
237The general form of a move is
239 >-app/vane-specific part of move
240`[bone term wire *]`
241 -arvo part---->
242 of move
244Or, in pseudo-code:
246`["cause" sys-call/action tack-new-layer-on-cause action-specific
247information]`
249Let's walk through each of these elements step by step
251#### Bones ("cause")
253If you look up `++bone` in `hoon.hoon`, you'll see that it's a
254number (`@ud`), and that it's an opaque reference to a duct.
255`++duct` in hoon.hoon is a list of `wire`s, where `wire` is an
256alias for `path`. `++path` is a list of `span`s, which are ASCII
257text. Thus, a duct is a list of paths, and a bone is an opaque
258reference to it (in the same way that a Unix file
259descriptor is an opaque reference to a file structure). Thus, to
260truly understand bones, we must understand ducts.
262A duct is stack of causes, again, represented as paths, which are
263called wires. At the bottom of every duct is a unix event, such
264as a keystroke, network packet, file change, or timer event.
265When arvo is given this event, it routes the event to appropriate
266kernel module for handling.
268XX[short explanation of the composition of arvo should be here.]
270Sometimes, the module can immediately handle the event and
271produce any necessary results. For example, when we poked the
272`++poke-atom` arm above, we poked %gall, our application server,
273which was able to respond to our poke directly. When the module
274cannot service the request itself, it sends instructions to the other kernel
275module or application to do a specified action, and produces the
276result from that. Furthermore, when one module sends a message to another kernel
277module or application, it also sends along the duct it was given
278with its new wire tacked onto the end. Now the duct has two entries,
279with the unix even on the bottom and the kernel module that
280handled it next. This process can continue indefinitely, adding
281more and more layers onto the duct. When an entity finally
282produces a result, a layer is popped off the duct, and the result
283is passed all the way back down.
285In effect, a duct is an arvo-level call stack. It's worth noting
286that while in traditional call stacks a function call happens
287synchronously and returns exactly once. In arvo, multiple moves
288can be sent at once, are evaluated asynchronously, and each
289one may be responded to zero or more times.
291The point to take home is that whatever caused `++poke-urbit` to be
292called is also the root cause for the network message we're
293trying to send. Thus, we say to send the network message along the given bone
294`ost`.
296##### Wire ('tack on new layer to duct')
298Of course, we have to push a new layer onto our duct
299before passing it along (or responding to it directly) anywhere This
300layer can have any data we want in it, but we don't need anything
301specific here, so we just use the wire `/sending`
302(`/elem1/elem2/elem n` is one syntax used to create `++path`s and
303`++wire`s of n elements). If we were expecting a response (which
304we're not), it would come back along the `/sending` wire, meaning
305that the path `/sending` will be passed back to the response
306handler as an argument. Although it's not required, it's
307generally a good idea to make the wire human-readable for
308bug-handling purposes.
310##### Term (sys-call)
312Looking back at the general form of a move, there is a
313`term`--which is text composed lowercase ascii and `-`, and has
314the sign of `@tas`-- which in this case is `%poke`. This is the
315name of the particular kind of move we're sending. If you think
316of a move as a syscall (which you should), then this `term` is
317the name of the syscall. Common ones include: `%poke`, to
318message an app; `%warp`, to read from the filesystem; `%wait`, to
319set a timer; and `%them`, to send an http request.
321XXDescribe where they can find a list of these things.
323The general form ends with `*` (a noun) since each type of move takes
324different data. In our case, a `%poke` move takes a target
325(urbit and app) and marked data and pokes the arm of the
326corresponding mark on that app on that urbit
327with that data. `[to-urbit-address %pong]` is the target urbit and app,
328`%atom` is the mark`, and `'howdy'` is the data.
330When arvo receives a `%poke` move, it calls the appropriate
331`++poke`. The same mechanism is used for sending messages
332between apps on the same urbit as for sending messages between
333apps on different urbits.
335> We said earlier that we're not expecting a response. This is
336> not entirely true: the `++coup` is called when we receive
337> acknowledgment that the `++poke` was called. We don't do
338> anything with this information right now, but we could.
340**Exercises**:
342- Extend either of the apps in the first two exercises to accept
343 input over the network in the same way as `pong`.
345- Modify `pong` to print out a message when it receives an ack.
347- Write two apps, `even` and `odd`. When you pass an atom to
348 `even`, check whether it's even. If so, divide it by two and
349 recurse; otherwise, poke `odd` with it. When `odd` recieves
350 an atom, check whether it's equal to one. If so, terminate,
351 printing "%success". Otherwise, check whether it's odd. If
352 so, multiply it by three, add one, and recurse; otherwise, poke
353 `even` with it. multiply it by three and add one. When either
354 app receives a number, print it out along with the name of the
355 app. In the end, you should be able to watch Collatz's
356 conjecture play out between the two apps. Sample output:
358```
359~fintud-macrep:dojo> :even &atom 18
360[%even 18]
361[%odd 9]
362[%even 28]
363[%even 14]
364[%odd 7]
365[%even 22]
366[%odd 11]
367[%even 34]
368[%odd 17]
369[%even 52]
370[%even 26]
371[%odd 13]
372[%even 40]
373[%even 20]
374[%even 10]
375[%odd 5]
376[%even 16]
377[%even 8]
378[%even 4]
379[%even 2]
380%success
381```
383- Put `even` and `odd` on two separate ships and pass the
384 messages over the network.