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