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