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