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