on 459c430 help sign in
urbit.org/docs/arvo/subscriptions.md
268 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 /the-path hid)
30 |=({o/bone *} `move`[o %diff %noun arg])
31 ++ peer
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] /the-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 `^`. <!--XX this is a union, right?-->
111- You may have noticed the separate `|%`
112 ([barcen](../../hoon/twig/bar-core/cen-core/)) above the application core
113 `|_` ([barcab](../../hoon/twig/bar-core/cab-door/). We usually put our
114 types in another core on top of the application core. We can access
115 these type from our `|_` because in `hoon.hoon` files, all cores are
116 called against each other. (The shorthand for 'called' is `=>`.)
117 Thus, the `|%` with the types is in the context of the `|_`, as it
118 lies above it: `hoon.hoon` `=> |% w types => |_`
120Here's some sample output of the two working together:
123 ~fintud-macrep:dojo> |start %examples-source
124 >=
125 ~fintud-macrep:dojo> |start %examples-sink
126 >=
127 ~fintud-macrep:dojo> :examples-sink %on
128 [%subscribed-to pax=/the-path]
129 %successfully-subscribed]
130 >=
131 ~fintud-macrep:dojo> :examples-source 5
132 [%received-data 5]
133 >=
134 ~fintud-macrep:dojo> :examples-sink %off
135 >=
136 ~fintud-macrep:dojo> :examples-source 6
137 >=
138 ~fintud-macrep:dojo> :examples-sink %on
139 [%subscribed-to pax=/the-path]
140 %successfully-subscribed]
141 >=
142 ~fintud-macrep:dojo> :examples-source 7
143 [%received-data 7]
144 >=
146### :examples-source
148Hopefully you can get a sense for what's happening here. When we poke
149`:examples-sink` with `%on`, `:examples-sink` subscribes to
150`:examples-source`, and so whenever we poke `:examples-source`,
151`:examples-sink` gets the update and prints it out. Then we unsubscribe
152by poking `:examples-sink` with `%off`, and `:examples-sink` stops
153getting updates. We then resubscribe.
155There's a fair bit going on in this code. Let's look at
156`:examples-source` first.
158Our definition of `move` is fairly specific, since we're only going to
159sending one kind of move. The `%diff` move is a subscription update, and
160its content is marked data which gall routes to our subscribers.
162This is a slightly different kind of move than [we've dealt with so
163far](/arvo/network). It's producing a result rather than calling other
164code (i.e. it's a return rather than a function call), so if you recall
165the discussion of ducts, a layer gets popped off the duct rather than
166added to it. This is why no wire is needed for the move -- we won't
167receive anything in response to it.
169Anyways, there're two functions inside the `|_`. We already know when
170`++poke-noun` is called. `++peer` is called when someone tries to
171subscribe to our app. Of course, you don't just subscribe to an app; you
172subscribe to a path on that app. This path comes in as the argument to
173`++peer`.
175In our case, we don't care what path you subscribed on, and all we do is
176print out that you subscribed. Arvo keeps track of your subscriptions,
177so you don't have to. You can access your subscribers by looking at
178`sup` in the bowl that's passed in. `sup` is of type
179`(map bone {@p path})`, which associates bones with the urbit who
180subscribed, and which path they subscribed on. If you want to
181communicate with your subscribers, send them messages along their bone.
183`++poke-noun` "spams" the given argument to all our subscribers.
184There's a few things we haven't seen before. Firstly, `:_(a b)` is the
185same as `[b a]`. It's just a convenient way of formatting things when
186the first thing in a cell is much more complicated than the second.
187Thus, we're producing our state unchanged.
189Our list of moves is the result of a call to `++turn`. `++turn` is what
190many languages call "map" -- it runs a function on every item in a list
191and collects the results in a list. The list is `(prey /the-path hid)`
192and the function is the `|=` line right after it.
194`++prey` is a standard library function defined in `zuse`. It takes a
195path and a bowl and gives you a list of the subscribers who are
196subscribed on a path that begins with the given path. "Prey" is short
197for "prefix".
199Now we have the list of relevant subscribers. This a list of triples,
200`{bone @p path}`, where the only thing we really need is the bone,
201because we don't need to know their urbit or what exact path they
202subscribed on. Thus, our transformer function takes `{o/bone *}` and
203produces `[o %diff %noun arg]`, which is a move that provides bone `o`
204with this subscription update: `[%noun arg]`". This is fairly dense
205code, but what it's doing is straightforward.
207### :examples-sink
209`:examples-source` should now make sense. `:examples-sink` is a little
210longer, but not much more complicated.
212In `:examples-sink`, our definition of of `++move` is different. All
213moves start with a `bone`, and we conventionally refer to the second
214half as the "card", so that we can say a move is an action that sends a
215card along a bone.
217We have two kinds of cards here: we `%peer` to start a subscription, and
218we `%pull` to stop it. Both of these are "forward" moves that may
219receive a response, so they need a wire to tack onto the duct before
220they pass it on. They also need a target, which is a pair of an urbit
221and an app name. Additionally, `%peer` needs a path on that app to
222subscribe too. `%pull` doesn't need this, because its semantics are to
223cancel any subscriptions coming over this duct. If your bone and wire
224are the same as when you subscribed, then the cancellation will happen
225correctly.
227The only state we need for `:examples-sink` is a boolean to indicate
228whether we're already subscribed to `:examples-source`. We use
229`available/?`, where `?` is the sign of type boolean (similar to `*`,
230`@`), which defaults to true (that is, `0`).
232In `++poke-noun` we check our input to see both if it's `%on` and we're
233available. If so, we produce the move to subscribe to
234`:examples-source`:
236 [ost %peer /subscribe [our %source] /the-path]
238Also, we set available to false (`|`) with `+>.$(available |)`.
240Otherwise, if our input is `%off` and we're already subscribed (i.e.
241`available` is false), then we unsubscribe from `:examples-source`:
243 [ost %pull /subscribe [our %source] ~]
245It's important to send over the same bone and wire (`/subscribe`) as the
246one we originally subscribed on.
248We also set `available` to true (`&`).
250If neither of these cases are true, then we print our current
251subscription state.
253`++diff-noun` is called when we get a `%diff` update along a
254subscription with a mark of `noun`. `++diff-noun` is given the wire that
255we originally passed with the `%peer` subscription request along and the
256data we got back. In our case we just print out the data.
258`++reap` is called when we receive an acknowledgment as to whether the
259subscription was handled successfully. You can remember that `++reap` is
260the counterpart to `++peer` as it's pronounced like 'peer' backwards.
261Similarly, `coup` is similar to 'poke' backwards.
263Moving forward, `++reap` is given the wire we attempted to subscribe
264over, possibly along with an error message in cases of failure.
265`(unit type)` means "either `~` or `[~ type]`, which means it's used
266like Haskell's "maybe" or C's nullability. If `error` is `~`, then the
267subscription was successful and we tell that to the user. Otherwise, we
268print out the error message.