on 911d8ed help sign in
urbit.org/docs/programming/system/subscriptions.md
276 lines 9.8 kB history raw
1---
2next: true
3sort: 5
4title: Subscriptions
5---
7We've dealt fairly extensively with "poke" messages to an app,
8but these are somewhat limited. A poke is a one-way message, but
9more often we want to subscribe to updates from another app. You
10could build a subscription model out of one-way pokes, but it's
11such a common pattern that it's built into arvo.
13Let's take a look at two apps, `:examples-source` and `:examples-sink`. First,
14`:examples-source`:
16```
17/? 314
18!:
19|%
20++ move {bone $diff mark *}
21--
22|_ {hid/bowl state/$~}
23++ poke-noun
24 |= arg/*
25 ^- {(list move) _+>.$}
26 :_ +>.$
27 %+ turn (prey /the-path hid)
28 |=({o/bone *} `move`[o %diff %noun arg])
29++ peer
30 |= pax/path
31 ^- {(list move) _+>.$}
32 ~& [%subscribed-to pax=pax]
33 [~ +>.$]
34--
35```
37And secondly, `:examples-sink`:
39```
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 ~& [%recieved-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--
73```
75Cheat sheet:
77```
78- `&` can either be the boolean true (along with `%.y`, `0`), or
79 the irregular wide form of the `$&` rune, which computes the
80 logical 'and' operation on its two children.
82- Similar to `&`,`|` is either the boolean false (along with `%.n` and `1`), or
83 the irregular short for of `?|`, which computes the logical 'or' operation on
84 its two children
86- `!` is the irregular wide form of `?!`, which computes the logical `not` on
87 its child.
89- `?~` is basically an if-then-else statement that checks whether condition `p`
90 is `~` (null). `?~` is slightly different to `?:(~ %tru %fal) in that it
91 reduces to ?:($=(%type value) %tru %false). `$=` tests whether value `q`
92 falls within type `p`. One thing to watch out for in hoon: if you do `?~`, it
93 affects the type of the conditional value: XXexample
95- `:_` is just an inverted `:-`: all it does is accept children `p` and `q` and
96 produce an inverted tuple of the two, `[q p]`.
98- `++bowl` is the type of our system state within our app. For example, it
99 includes things like `our`, the ship name of the host, and `now`, the
100 current time.
102- `$%` is a type constructor that defines a type composed of `n`
103 types that it is passed. For example `$% @ * ^ ==` is the
104 type of either `@`, `*`, or a cell `^`. XX this a union, right?
106- You may have noticed the separate `|%` above the application
107 core `|_`. We usually put our types in another core on top of the
108 application core. We can access these type from our `|_`
109 because in hoon.hoon files, all cores are =>'ed (called) against
110 each other. Thus the `|%` with the types is in the context of
111 the `|_`, as it lies above it: hoon.hoon `=> |% w types =>
112 |_`
115```
117Here's some sample output of the two working together:
119```
120~fintud-macrep:dojo> |start %examples-source
121>=
122~fintud-macrep:dojo> |start %examples-sink
123>=
124~fintud-macrep:dojo> :examples-sink %on
125[%subscribed-to pax=/the-path]
126%successfully-subscribed]
127>=
128~fintud-macrep:dojo> :examples-source 5
129[%received-data 5]
130>=
131~fintud-macrep:dojo> :examples-sink %off
132>=
133~fintud-macrep:dojo> :examples-source 6
134>=
135~fintud-macrep:dojo> :examples-sink %on
136[%subscribed-to pax=/the-path]
137%successfully-subscribed]
138>=
139~fintud-macrep:dojo> :examples-source 7
140[%received-data 7]
141>=
142```
144###:examples-source
146Hopefully you can get a sense for what's happening here. When we
147poke `:examples-sink` with `%on`, `:examples-sink` subscribes to `:examples-source`, and so
148whenever we poke `:examples-source`, `:examples-sink` gets the update and prints it
149out. Then we unsubscribe by poking `:examples-sink` with `%off`, and
150`:examples-sink` stops getting updates. We then resubscribe.
152There's a fair bit going on in this code. Let's look at
153`:examples-source` first.
155Our definition of `move` is fairly specific, since we're only
156going to sending one kind of move. The `%diff` move is a
157subscription update, and its content is marked data which gall
158routes to our subscribers.
160This is a slightly different kind of move than we've dealt with
161so far. It's producing a result rather than calling other code
162(i.e. it's a return rather than a function call), so if you
163recall the discussion of ducts, a layer gets popped off the duct
164rather than added to it. This is why no wire is needed for the
165move -- we won't receive anything in response to it.
167Anyways, there're two functions inside the `|_`. We already know
168when `++poke-noun` is called. `++peer` is called when someone
169tries to subscribe to our app. Of course, you don't just
170subscribe to an app; you subscribe to a path on that app. This
171path comes in as the argument to `++peer`.
173In our case, we don't care what path you subscribed on, and all
174we do is print out that you subscribed. Arvo keeps track of your
175subscriptions, so you don't have to. You can access your
176subscribers by looking at `sup` in the bowl that's passed in.
177`sup` is of type `(map bone {@p path})`, which associates bones
178with the urbit who subscribed, and which path they subscribed on.
179If you want to communicate with your subscribers, send them messages along
180their bone.
182`++poke-noun` "spams" the given arguement to all our subscribers.
183There's a few things we haven't seen before. Firstly, `:_(a b)`
184is the same as `[b a]`. It's just a convenient way of formatting
185things when the first thing in a cell is much more complicated
186than the second. Thus, we're producing our state unchanged.
188Our list of moves is the result of a call to `++turn`. `++turn`
189is what many languages call "map" -- it runs a function on every
190item in a list and collects the results in a list. The list is
191`(prey /the-path hid)` and the function is the `|=` line right
192after it.
194`++prey` is a standard library function defined in `zuse`. It
195takes a path and a bowl and gives you a list of the subscribers
196who are subscribed on a path that begins with the given path.
197"Prey" is short for "prefix".
199Now we have the list of relevant subscribers. This a list of
200triples, `{bone @p path}`, where the only thing we really need is
201the bone because we don't care what urbit they are or what exact
202path they subscribed on. Thus, our transformer function takes
203`{o/bone *}` and produces `[o %diff %noun arg]`, which is a move
204that provides bone `o` with this subscription update: `[%noun
205arg]`". This is fairly dense code, but what it's doing is
206straightforward.
208###:examples-sink
210`:examples-source` should now make sense. `:examples-sink` is a little longer,
211but 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
215second half as the "card", so that we can say a move is an action
216that sends a card along a bone.
218We have two kinds of cards here: we `%peer` to start a
219subscription, and we `%pull` to stop it. Both of these are
220"forward" moves that may receive a response, so they need a wire
221to tack onto the duct before they pass it on. They also need a
222target, which is a pair of an urbit and an app name. Additionally, `%peer`
223needs a path on that app to subscribe too. `%pull` doesn't need
224this, because its semantics are to cancel any subscriptions
225coming over this duct. If your bone and wire are the same as
226when you subscribed, then the cancellation will happen correctly.
228The only state we need for `:examples-sink` is a boolean to indicate whether
229we're already subscribed to `:examples-source`. We use `available/?`,
230where `?` is the sign of type boolean (similar to `*`, `@`) (which defaults to true).
232In `++poke-noun` we check our input to see both if it's `%on` and
233we're available. If so, we produce the move to subscribe to
234`:examples-source`:
236```
237[ost %peer /subscribe [our %source] /the-path]
238```
240Also, we set available to false (`|`) with `+>.$(available |)`.
242Otherwise, if our input is `%off` and we're already subscribed
243(i.e. `available` is false), then we unsubscribe from
244`:examples-source`:
246```
247[ost %pull /subscribe [our %source] ~]
248```
250It's important to send over the same bone and wire (`/subscribe`)
251as the one we originally subscribed on.
253We also set `available` to true (`&`).
255If neither of these cases are true, then we just print out our
256current subscription state and do nothing.
258`++diff-noun` is called when we get a `%diff` update along a
259subscription with a mark of `noun`. `++diff-noun` is given the
260wire that we originally passed with the `%peer` subscription
261request along and the data we got back. In our case we just
262print out the data.
264`++reap` is called when we receive an acknowledgment as to
265whether the subscription was handled successfully. You can
266remember that `++reap` is the counterpart to `++peer` as it's
267almost spelled the same as 'peer' backwards. Similarly, `coup` is
268similar to 'poke' backwards.
270Moving forward, `++reap` is given the wire we attempted to
271subscribe over, possibly along with an error message in cases of
272failure. `(unit type)` means "either `~` or `[~ type]`, which
273means it's used like Haskell's "maybe" or C's nullability. If
274`error` is `~`, then the subscription was successful and we tell
275that to the user. Otherwise, we print out the error message.