on 3c19574 help sign in
urbit.org/docs/arvo/subscriptions.md
347 lines 12 kB history raw
1---
2navhome: /docs/
3next: true
4sort: 15
5title: Subscriptions
6---
8# Subscriptions
10We've dealt fairly extensively with "poke" messages to an app, but these
11are somewhat limited. A poke is a one-way message, but more often we
12want to subscribe to updates from another app. You could build a
13subscription model out of one-way pokes, but it's such a common pattern
14that it's built into arvo.
16Let's take a look at two apps, `:source` and `:sink`. First,
17`:source`:
19 :: Sends subscription updates to sink.hoon
20 ::
21 :::: /===/app/source/hoon
22 ::
23 !:
24 ::
25 |%
26 ++ move {bone card}
27 ++ card $% {$diff diff-contents}
28 ==
29 ++ diff-contents $% {$noun *}
30 ==
31 --
32 ::
33 |_ {bow/bowl $~}
34 ::
35 ++ poke-noun
36 |= non/*
37 ^- {(list move) _+>.$}
38 :_ +>.$
39 %+ turn (prey /example-path bow)
40 |=({o/bone *} [o %diff %noun non])
41 ::
42 ++ peer-example-path
43 |= pax/path
44 ^- {(list move) _+>.$}
45 ~& source+peer-notify+'Someone subscribed to you!'
46 ~& source+[ship+src.bow path+pax]
47 [~ +>.$]
48 ::
49 ++ coup
50 |= {wir/wire err/(unit tang)}
51 ^- {(list move) _+>.$}
52 ?~ err
53 ~& source+success+'Poke succeeded!'
54 [~ +>.$]
55 ~& source+error+'Poke failed. Error:'
56 ~& source+error+err
57 [~ +>.$]
58 ::
59 ++ reap
60 |= {wir/wire err/(unit tang)}
61 ^- {(list move) _+>.$}
62 ?~ err
63 ~& source+success+'Peer succeeded!'
64 [~ +>.$]
65 ~& source+error+'Peer failed. Error:'
66 ~& source+error+err
67 [~ +>.$]
68 ::
69 --
71And secondly, `:sink`:
73 :: Sets up a simple subscription to source.hoon
74 ::
75 :::: /===/app/sink/hoon
76 ::
77 !:
78 |%
79 ++ move {bone card}
80 ++ card $% {$peer wire dock path}
81 {$pull wire dock $~}
82 ==
83 --
84 ::
85 |_ {bow/bowl val/?}
86 ::
87 ++ poke-noun
88 |= non/*
89 ^- {(list move) _+>.$}
90 ?: &(=(%on non) val)
91 :_ +>.$(val |)
92 :~ :* ost.bow
93 %peer
94 /subscribe
95 [our.bow %source]
96 /example-path
97 ==
98 ==
99 ?: &(=(%off non) !val)
100 :_ +>.$(val &)
101 ~[[ost.bow %pull /subscribe [our.bow %source] ~]]
102 ~& ?: val
103 sink+unsubscribed+'You are now unsubscribed!'
104 sink+subscribed+'You are now subscribed!'
105 [~ +>.$]
106 ::
107 ++ diff-noun
108 |= {wir/wire non/*}
109 ^- {(list move) _+>.$}
110 ~& sink+received-data+'You got something!'
111 ~& sink+data+non
112 [~ +>.$]
113 ::
114 ++ coup
115 |= {wir/wire err/(unit tang)}
116 ^- {(list move) _+>.$}
117 ?~ err
118 ~& sink+success+'Poke succeeded!'
119 [~ +>.$]
120 ~& sink+error+'Poke failed. Error:'
121 ~& sink+error+err
122 [~ +>.$]
123 ::
124 ++ reap
125 |= {wir/wire err/(unit tang)}
126 ^- {(list move) _+>.$}
127 ?~ err
128 ~& sink+success+'Peer succeeded!'
129 [~ +>.$]
130 ~& sink+error+'Peer f ailed. Error:'
131 ~& sink+error+err
132 [~ +>.$]
133 ::
134 --
136Cheat sheet:
138- `&` (pam) can either be the boolean true (as can `%.y`, `0`), or
139 the irregular wide form of the `?&`
140 ([wutpam](../../hoon/twig/wut-test/pam-and)) rune, which computes
141 logical `AND` on its two children.
143- Similar to `&`,`|` is either the boolean false (along with `%.n` and
144 `1`), or the irregular short for of `?|`
145 ([wutbar](../../hoon/twig/wut-test/bar-or)), which computes logical `OR`
146 on its two children.
148- `!` is the irregular wide form of `?!`
149 ([wutzap](../../hoon/twig/wut-test/zap-not/)), which computes logical
150 `NOT` on its child.
152- `?~` ([wutsig](../../hoon/twig/wut-test/sig-ifno/)) is basically an
153 if-then-else that checks whether condition `p` is `~` (null). `?~`
154 is slightly different from `?:(~ %tru %fal)` in that `?~` reduces to
155 `?:($=(%type value) %tru %false)`. `$=`
156 ([buctis](../../hoon/twig/buc-mold/tis-coat/)) tests whether value `q` is
157 of type `p`.
158<!-- One thing to watch out for in hoon: if you do `?~`, it
159 affects the type of the conditional value: XXexample -->
161- `:_` ([colcab](../../hoon/twig/col-cell/cab-scon/)) is inverted `:-`: it
162 accepts `p` and `q`, and produces `[q p]`.
164- `++bowl` is the type of the system state within our app. For
165 example, it includes things like `our`, the name of the host urbit,
166 and `now`, the current time.
168- `$%` ([buccen](../../hoon/twig/buc-mold/cen-book/)) is a type
169 constructor: it defines a new type, composed of `n` types that it is
170 passed. For example `$% @ * ^ ==` is the type of either `@`,
171 `*`, or a cell `^`.
172<!-- XX this is a union, right? -->
174- You may have noticed the separate `|%`
175 ([barcen](../../hoon/twig/bar-core/cen-core/)) above the application core
176 `|_` ([barcab](../../hoon/twig/bar-core/cab-door/). We usually put our
177 types in another core on top of the application core. We can access
178 these type from our `|_` because in `hoon.hoon` files, all cores are
179 called against each other. (The shorthand for 'called' is `=>`.)
180 Thus, the `|%` with the types is in the context of the `|_`, as it
181 lies above it: `hoon.hoon` `=> |% w types => |_`
183Here's some sample output of the two working together:
185 ~fintud-macrep:dojo> |start %source
186 >=
187 ~fintud-macrep:dojo> |start %sink
188 >=
189 ~fintud-macrep:dojo> :sink %on
190 [%source %peer-notify 'Someone subscribed to you!']
191 [%source [%ship ~fintud-macrep] %path /]
192 [%sink %success 'Peer succeeded!']
193 >=
194 ~fintud-macrep:dojo> :source 5
195 [%sink %received-data 'You got something!']
196 [%sink %data 5]
197 >=
198 ~fintud-macrep:dojo> :sink %off
199 >=
200 ~fintud-macrep:dojo> :source 6
201 >=
202 ~fintud-macrep:dojo> :sink %on
203 [%source %peer-notify 'Someone subscribed to you!']
204 [%source [%ship ~fintud-macrep] %path /]
205 [%sink %success 'Peer succeeded!']
206 >=
207 ~fintud-macrep:dojo> :source 7
208 [%sink %received-data 'You got something!']
209 [%sink %data 7]
210 >=
212### :source
214Hopefully you can get a sense for what's happening here. When we poke
215`:sink` with `%on`, `:sink` subscribes to `:source`,
216and so whenever we poke `:source`, `:sink` gets the update and
217prints it out. Then we unsubscribe by poking `:sink` with `%off`, and
218`:sink` stops getting updates. We then resubscribe.
220There's a fair bit going on in this code. Let's look at `:source`
221first.
223Our definition of `move` is fairly specific, since we're only going to
224sending one kind of move. The `%diff` move is a subscription update, and
225its content is marked data which gall routes to our subscribers.
227This is a slightly different kind of move than [we've dealt with so
228far](/arvo/network). It's producing a result rather than calling other
229code (i.e. it's a return rather than a function call), so if you recall
230the discussion of ducts, a layer gets popped off the duct rather than
231added to it. This is why no wire is needed for the move -- we won't
232receive anything in response to it.
234Anyways, there are four functions (arms) inside the `|_`. We already know when
235`++poke-noun` is called. `++peer-example-path` is called when someone tries to
236subscribe to our app. Of course, you don't just subscribe to an app; you
237subscribe to a path on that app. This path comes in as the argument to `++peer`.
239In our case, we don't care what path you subscribed on, and all we do is print
240out that you subscribed. Arvo keeps track of your subscriptions, so you don't
241have to. You can access your subscribers by looking at `sup` in the bowl that's
242passed in. `sup` is of type `(map bone {@p path})`, which associates bones with
243the urbit who subscribed, and which path they subscribed on. If you want to
244communicate with your subscribers, send them messages along their bone.
246`++poke-noun` "spams" the given argument to all our subscribers.
247There's a few things we haven't seen before. Firstly, `:_(a b)` is the
248same as `[b a]`. It's just a convenient way of formatting things when
249the first thing in a cell is much more complicated than the second.
250Thus, we're producing our state unchanged.
252Our list of moves is the result of a call to `++turn`. `++turn` is what many
253languages call "map" -- it runs a function on every item in a list and collects
254the results in a list. The list is `(prey /example-path bow)` and the function
255is the `|=` line right after it.
257`++prey` is a standard library function defined in `zuse.hoon`. It takes a path
258and a bowl and gives you a list of the subscribers who are subscribed on a path
259that begins with the given path. "Prey" is short for "prefix".
261Now we have the list of relevant subscribers. This a list of triples,
262`{bone @p path}`, where the only thing we really need is the bone, because we
263don't need to know their urbit or what exact path they subscribed on. Thus, our
264transformer function takes `{o/bone *}` and produces `[o %diff %noun non]`,
265which is a move that provides bone `o` with this subscription update:
266`[%noun non]`". This is fairly dense code, but what it's doing is
267straightforward!
269### :sink
271`:source` should now make sense. `:sink` is a little longer, but not much more
272complicated.
274In `:sink`, our definition of of `++move` is different. All moves start
275with a `bone`, and we conventionally refer to the second half as the "card", so
276that we can say a move is an action that sends a card along a bone.
278We have two kinds of cards here: we `%peer` to start a subscription, and we
279`%pull` to stop it. Both of these are "forward" moves that may receive a
280response, so they need a wire to tack onto the duct before they pass it on. They
281also need a target, which is a pair of an urbit and an app name. Additionally,
282`%peer` needs a path on that app to subscribe too. `%pull` doesn't need this,
283because its semantics are to cancel any subscriptions coming over this duct. If
284your bone and wire are the same as when you subscribed, then the cancellation
285will happen correctly.
287The only state we need for `:sink` is a boolean to indicate whether
288we're already subscribed to `:source`. We use `val/?`, where `?`
289is the sign of type boolean (similar to `*`, `@`), which defaults to true (that
290is, `0`).
292In `++poke-noun` we check our input to see both if it's `%on` and we're
293available (`val` is true). If so, we produce the move to subscribe to
294`:source`:
296 :~ :* ost.bow
297 %peer
298 /subscribe
299 [our.bow %source]
300 /example-path
301 ==
302 ==
304Also, in the preceding lines, we set `val` to false (`|`) with `+>.$(val |)`.
305Remember that the `:_` constructs an inverted cell, with the first child
306(`+>.$(val |` in our case) as the tail and the second child as the head. Here,
307the cell we produce when our subscription is `%on` and `val` is true has a
308head with our new state where `val` is set to false and a tail of our list of
309moves, which is shown in the code block above.
311Otherwise, if our input is `%off` and we're already subscribed (i.e. `val`
312is false), then we unsubscribe from `:source` and set `val` back to true (`&`),
313again using our handy inverted cell constructor mold `:_`:
315 :_ +>.$(val &)
316 ~[[ost.bow %pull /subscribe [our.bow %source] ~]]
318It's important to send over the same bone and wire (`/subscribe`) as the
319one we originally subscribed on.
321If neither of these cases are true, then we print our current subscription
322state, based on whether `val` is true or false, and return a cell containing
323a null list of moves and our unchanged app state:
325 ~& ?: val
326 sink+unsubscribed+'You are now unsubscribed!'
327 sink+subscribed+'You are now subscribed!'
328 [~ +>.$]
330`++diff-noun` is called when we get a `%diff` update along a subscription with a
331mark of `noun`. `++diff-noun` is given the wire that we originally passed with
332the `%peer` subscription request along and the data we got back. In our case we
333just print out the data:
335 ~& sink+received-data+'You got something!'
336 ~& sink+data+non
338`++reap` is called when we receive an acknowledgment as to whether the
339subscription was handled successfully. You can remember that `++reap` is the
340counterpart to `++peer` as it's pronounced like 'peer' backwards. Similarly,
341`coup` is similar to 'poke' backwards.
343Moving forward, `++reap` is given the wire we attempted to subscribe over,
344possibly along with an error message in cases of failure. `(unit type)` means
345"either `~` or `[~ type]`, which means it's used like Haskell's "maybe" or C's
346nullability. If `err` is `~`, then the subscription was successful and we tell
347that to the user. Otherwise, we print out the error message.