on 6d45ede help sign in
urbit.org/docs/arvo/subscriptions.md
269 lines 10 kB history raw
1---
2navhome: /docs
3next: true
4sort: 5
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, `:examples-source` and `:examples-sink`.
17First, `:examples-source`:
19 /? 314
20 !:
21 |%
22 ++ move {bone $diff mark *}
23 --
24 |_ {hid/bowl state/$~}
25 ++ poke-noun
26 |= arg/*
27 ^- {(list move) _+>.$}
28 :_ +>.$
29 %+ turn (prey /example-path hid)
30 |=({o/bone *} `move`[o %diff %noun arg])
31 ++ peer-example-path
32 |= pax/path
33 ^- {(list move) _+>.$}
34 ~& [%subscribed-to pax=pax]
35 [~ +>.$]
36 --
38And secondly, `:examples-sink`:
40 /? 314
41 |%
42 ++ move {bone card}
43 ++ card
44 $% {$peer wire {@p term} path}
45 {$pull wire {@p term} $~}
46 ==
47 --
48 !:
49 |_ {bowl available/?}
50 ++ poke-noun
51 |= arg/*
52 ^- {(list move) _+>.$}
53 ?: &(=(%on arg) available)
54 [[[ost %peer /subscribe [our %source] /example-path] ~] +>.$(available |)]
55 ?: &(=(%off arg) !available)
56 [[[ost %pull /subscribe [our %source] ~] ~] +>.$(available &)]
57 ~& ?:(available %not-subscribed %subscribed)
58 [~ +>.$]
59 ++ diff-noun
60 |= {wir/wire arg/*}
61 ^- {(list move) _+>.$}
62 ~& [%received-data arg]
63 [~ +>.$]
64 ++ reap
65 |= {wir/wire error/(unit tang)}
66 ^- {(list move) _+>.$}
67 ?~ error
68 ~& %successfully-subscribed
69 [~ +>.$]
70 ~& [%subscription-failed error]
71 [~ +>.$]
72 --
74Cheat sheet:
76- `&` (pam) can either be the boolean true (as can `%.y`, `0`), or
77 the irregular wide form of the `?&`
78 ([wutpam](../../hoon/twig/wut-test/pam-and)) rune, which computes
79 logical `AND` on its two children.
81- Similar to `&`,`|` is either the boolean false (along with `%.n` and
82 `1`), or the irregular short for of `?|`
83 ([wutbar](../../hoon/twig/wut-test/bar-or)), which computes logical `OR`
84 on its two children.
86- `!` is the irregular wide form of `?!`
87 ([wutzap](../../hoon/twig/wut-test/zap-not/)), which computes logical
88 `NOT` on its child.
90- `?~` ([wutsig](../../hoon/twig/wut-test/sig-ifno/)) is basically an
91 if-then-else that checks whether condition `p` is `~` (null). `?~`
92 is slightly different from `?:(~ %tru %fal)` in that `?~` reduces to
93 `?:($=(%type value) %tru %false)`. `$=`
94 ([buctis](../../hoon/twig/buc-mold/tis-coat/)) tests whether value `q` is
95 of type `p`.
96<!-- One thing to watch out for in hoon: if you do `?~`, it
97 affects the type of the conditional value: XXexample -->
99- `:_` ([colcab](../../hoon/twig/col-cell/cab-scon/)) is inverted `:-`: it
100 accepts `p` and `q`, and produces `[q p]`.
102- `++bowl` is the type of the system state within our app. For
103 example, it includes things like `our`, the name of the host urbit,
104 and `now`, the current time.
106- `$%` ([buccen](../../hoon/twig/buc-mold/cen-book/)) is a type
107 constructor: it defines a new type, composed of `n` types that it is
108 passed. For example `$% @ * ^ ==` is the type of either `@`,
109 `*`, or a cell `^`.
110<!-- XX this is a union, right? -->
112- You may have noticed the separate `|%`
113 ([barcen](../../hoon/twig/bar-core/cen-core/)) above the application core
114 `|_` ([barcab](../../hoon/twig/bar-core/cab-door/). We usually put our
115 types in another core on top of the application core. We can access
116 these type from our `|_` because in `hoon.hoon` files, all cores are
117 called against each other. (The shorthand for 'called' is `=>`.)
118 Thus, the `|%` with the types is in the context of the `|_`, as it
119 lies above it: `hoon.hoon` `=> |% w types => |_`
121Here's some sample output of the two working together:
124 ~fintud-macrep:dojo> |start %examples-source
125 >=
126 ~fintud-macrep:dojo> |start %examples-sink
127 >=
128 ~fintud-macrep:dojo> :examples-sink %on
129 [%subscribed-to pax=/example-path]
130 %successfully-subscribed]
131 >=
132 ~fintud-macrep:dojo> :examples-source 5
133 [%received-data 5]
134 >=
135 ~fintud-macrep:dojo> :examples-sink %off
136 >=
137 ~fintud-macrep:dojo> :examples-source 6
138 >=
139 ~fintud-macrep:dojo> :examples-sink %on
140 [%subscribed-to pax=/example-path]
141 %successfully-subscribed]
142 >=
143 ~fintud-macrep:dojo> :examples-source 7
144 [%received-data 7]
145 >=
147### :examples-source
149Hopefully you can get a sense for what's happening here. When we poke
150`:examples-sink` with `%on`, `:examples-sink` subscribes to
151`:examples-source`, and so whenever we poke `:examples-source`,
152`:examples-sink` gets the update and prints it out. Then we unsubscribe
153by poking `:examples-sink` with `%off`, and `:examples-sink` stops
154getting updates. We then resubscribe.
156There's a fair bit going on in this code. Let's look at
157`:examples-source` first.
159Our definition of `move` is fairly specific, since we're only going to
160sending one kind of move. The `%diff` move is a subscription update, and
161its content is marked data which gall routes to our subscribers.
163This is a slightly different kind of move than [we've dealt with so
164far](/arvo/network). It's producing a result rather than calling other
165code (i.e. it's a return rather than a function call), so if you recall
166the discussion of ducts, a layer gets popped off the duct rather than
167added to it. This is why no wire is needed for the move -- we won't
168receive anything in response to it.
170Anyways, there're two functions inside the `|_`. We already know when
171`++poke-noun` is called. `++peer` is called when someone tries to
172subscribe to our app. Of course, you don't just subscribe to an app; you
173subscribe to a path on that app. This path comes in as the argument to
174`++peer`.
176In our case, we don't care what path you subscribed on, and all we do is
177print out that you subscribed. Arvo keeps track of your subscriptions,
178so you don't have to. You can access your subscribers by looking at
179`sup` in the bowl that's passed in. `sup` is of type
180`(map bone {@p path})`, which associates bones with the urbit who
181subscribed, and which path they subscribed on. If you want to
182communicate with your subscribers, send them messages along their bone.
184`++poke-noun` "spams" the given argument to all our subscribers.
185There's a few things we haven't seen before. Firstly, `:_(a b)` is the
186same as `[b a]`. It's just a convenient way of formatting things when
187the first thing in a cell is much more complicated than the second.
188Thus, we're producing our state unchanged.
190Our list of moves is the result of a call to `++turn`. `++turn` is what
191many languages call "map" -- it runs a function on every item in a list
192and collects the results in a list. The list is `(prey /example-path hid)`
193and the function is the `|=` line right after it.
195`++prey` is a standard library function defined in `zuse`. It takes a
196path and a bowl and gives you a list of the subscribers who are
197subscribed on a path that begins with the given path. "Prey" is short
198for "prefix".
200Now we have the list of relevant subscribers. This a list of triples,
201`{bone @p path}`, where the only thing we really need is the bone,
202because we don't need to know their urbit or what exact path they
203subscribed on. Thus, our transformer function takes `{o/bone *}` and
204produces `[o %diff %noun arg]`, which is a move that provides bone `o`
205with this subscription update: `[%noun arg]`". This is fairly dense
206code, but what it's doing is straightforward.
208### :examples-sink
210`:examples-source` should now make sense. `:examples-sink` is a little
211longer, but not much more complicated.
213In `:examples-sink`, our definition of of `++move` is different. All
214moves start with a `bone`, and we conventionally refer to the second
215half as the "card", so that we can say a move is an action that sends a
216card along a bone.
218We have two kinds of cards here: we `%peer` to start a subscription, and
219we `%pull` to stop it. Both of these are "forward" moves that may
220receive a response, so they need a wire to tack onto the duct before
221they pass it on. They also need a target, which is a pair of an urbit
222and an app name. Additionally, `%peer` needs a path on that app to
223subscribe too. `%pull` doesn't need this, because its semantics are to
224cancel any subscriptions coming over this duct. If your bone and wire
225are the same as when you subscribed, then the cancellation will happen
226correctly.
228The only state we need for `:examples-sink` is a boolean to indicate
229whether we're already subscribed to `:examples-source`. We use
230`available/?`, where `?` is the sign of type boolean (similar to `*`,
231`@`), which defaults to true (that is, `0`).
233In `++poke-noun` we check our input to see both if it's `%on` and we're
234available. If so, we produce the move to subscribe to
235`:examples-source`:
237 [ost %peer /subscribe [our %source] /example-path]
239Also, we set available to false (`|`) with `+>.$(available |)`.
241Otherwise, if our input is `%off` and we're already subscribed (i.e.
242`available` is false), then we unsubscribe from `:examples-source`:
244 [ost %pull /subscribe [our %source] ~]
246It's important to send over the same bone and wire (`/subscribe`) as the
247one we originally subscribed on.
249We also set `available` to true (`&`).
251If neither of these cases are true, then we print our current
252subscription state.
254`++diff-noun` is called when we get a `%diff` update along a
255subscription with a mark of `noun`. `++diff-noun` is given the wire that
256we originally passed with the `%peer` subscription request along and the
257data we got back. In our case we just print out the data.
259`++reap` is called when we receive an acknowledgment as to whether the
260subscription was handled successfully. You can remember that `++reap` is
261the counterpart to `++peer` as it's pronounced like 'peer' backwards.
262Similarly, `coup` is similar to 'poke' backwards.
264Moving forward, `++reap` is given the wire we attempted to subscribe
265over, possibly along with an error message in cases of failure.
266`(unit type)` means "either `~` or `[~ type]`, which means it's used
267like Haskell's "maybe" or C's nullability. If `error` is `~`, then the
268subscription was successful and we tell that to the user. Otherwise, we
269print out the error message.