on 932d391 help sign in
250 lines 8.0 kB history raw
1---
2next: true
3sort: 8
4title: Using Generators with Apps
5---
7Up until now we've poked apps directly. This requires the user
8to specify the mark, and it requires the app to accept the
9arguments in a way that's convenient for users to input. This is
10the "plumbing" way to interact with apps. Generators are the
11"porcelain" layer. This is why when you run a command like `+ls` or
12`|merge`, there are no marks in sight.
14We've used generators before, back in [Basic
15Operation](http://urbit.org/docs/user/basic). At that point, we just used the generators
16to produce values -- we didn't pipe their results into apps. In
17the dojo cast, the role of a generator is to take a list of
18arguments and produce a value, which is often, though not always,
19piped into an app. Generators are pure, stateless functions, and
20they cannot send moves.
22`+ls` and `+cat` are commonly-used generators that produce a directory
23listing and print out a file, respectively. These are useful in
24themselves, so we generally don't pipe the results into an app.
26Another generator is `+hood/merge`. Try running `+hood/merge`
27with the normal arguments for `|merge`:
29```
30~fintud-macrep:dojo> +hood/merge %home our %they
31[syd=%home her=~fintud-macrep sud=%they gem=%auto]
32~fintud-macrep:dojo> +hood/merge %home our %they, =gem %this
33[syd=%home her=~fintud-macrep sud=%they gem=%auto]
34```
36This didn't run any merge, it just constructed the command that,
37if sent to `:hood`, would run it. Note first that, even though
38it's not printed, this value has mark `kiln-merge`. Also, the
39merge strategy `gem=%auto` was added automatically. Optional
40arguments are straightforward with generators.
42In general, generators are prepended by either `+` or `|`. The
43general form is `+generator`, but often generators are created
44specifically to work with a single app, they're usually placed in
45a subdirectory of gen and run with `|`. Thus, if `my-generator`
46generator is made for use with `:my-app`, then `my-generator` is
47put in `/gen/my-app/my-generator.hoon`, and can be run directly
48with `+my-app/my-generator <args>`. If you want to pipe the
49result into `:my-app`, then run `:my-app +my-app/my-generator
50<args>`. Because this pattern is so common, this can be
51abbreviated to `:my-app|my-generator <args>`. Because most
52built-in commands are generators for the `:hood` app,
53`:hood|generator <args>` can be shortened to `|generator <args>`.
55Let's write a generator for a modified version of `:pong` from
56the chapter on [Network Messages](http://urbit.org/docs/user/network). Recall that `:pong`
57takes an urbit address, which is of mark `urbit`, and sends that
58urbit the message `'howdy'`. First--without a generator--let's
59make `:ping` that does the same, except that it lets the user
60optionally specify the message as well.
62We'll need a new mark for our arguments. Let's call it
63`ping-message`.
65<blockquote class="blockquote">
66For app-specific marks, it's good style to prefix the name of
67the mark with the name of the app. Since many apps have
68several such marks, subdirectories in `/mar` are rendered as
69`-`, so that `ping-message` is written in
70(`/mar/ping/message.hoon`).
71</blockquote>
73```
74:: Up-ness monitor. Accepts atom url, 'on', or 'off'
75::
76:::: /hoon/up/examples/app
77 ::
78/? 314
79|%
80++ move {bone card}
81++ card
82 $% {$hiss wire $~ $httr {$purl p/purl}}
83 {$wait wire @da}
84 ==
85++ action
86 $% {$on $~} :: enable polling('on')
87 {$off $~} :: disable polling('off)
88 {$target p/cord} :: set poll target('http://...')
89 ==
90--
91|_ {hid/bowl on/_| in-progress/_| target/@t}
92++ poke-atom
93 |= url-or-command/@t ^- (quip move +>)
94 =+ ^- act/action
95 ?: ?=($on url-or-command) [%on ~]
96 ?: ?=($off url-or-command) [%off ~]
97 [%target url-or-command]
98 ?- -.act
99 $target [~ +>.$(target p.act)]
100 $off [~ +>.$(on |)]
101 $on
102 :- ?: |(on in-progress) ~
103 [ost.hid %hiss /request ~ %httr %purl (need (epur target))]~
104 +>.$(on &, in-progress &)
105 ==
106::
108:: ~& 'i get here'
109:: ^- {(list move) _+>.$}
110:: ?: =('off' url)
111:: [~ +>.$(on |)]
112:: ?: =('on' url)
113:: :_ +>.$(on &, in-progress &)
114:: ?: |(on in-progress)
115:: ~
116:: [ost.hid %them /request ~ (need (epur target)) %get ~ ~]~
117:: [~ +>.$(target url)]
118++ sigh-httr
119 |= {wir/wire code/@ud headers/mess body/(unit octs)}
120 ~& 'arrive here'
121 ^- {(list move) _+>.$}
122 ?: &((gte code 200) (lth code 300))
123 ~& [%all-is-well code]
124 :_ +>.$
125 [ost.hid %wait /timer (add ~s10 now.hid)]~
126 ~& [%we-have-a-problem code]
127 ~& [%headers headers]
128 ~& [%body body]
129 :_ +>.$
130 [ost.hid %wait /timer (add ~s10 now.hid)]~
131++ wake-timer
132 |= {wir/wire $~} ^- (quip move +>)
133 ?: on
134 :_ +>.$
135 [ost.hid %hiss /request ~ %httr %purl (need (epur target))]~
136 [~ +>.$(in-progress |)]
137::
138++ prep ~& target _`. ::
139--
140```
142The app can easily be modified to use this (`/app/ping.hoon`):
144```
145::
146:::: /hoon/ping/examples/app
147 ::
148/? 151
149|%
150 ++ move {bone term wire *}
151--
152!:
153|_ {bowl state/$~}
154::
155++ poke-examples-ping-message
156 |= {to/@p message/@t}
157 ~& 'sent'
158 ^- {(list move) _+>.$}
159 :- ^- (list move)
160 :~ `move`[ost %poke /sending [to dap] %atom message]
161 ==
162 +>.$
163::
164++ poke-atom
165 |= arg/@
166 ~& 'received'
167 ^- {(list move) _+>.$}
168 ::
169 ~& [%receiving (@t arg)]
170 [~ +>.$]
171::
172++ coup |=(* `+>)
173--
176```
178Now we can run this with:
180```
181~fintud-macrep:dojo> |start %ping
182>=
183~fintud-macrep:dojo> :ping &ping-message [~sampel-sipnym 'heyya']
184>=
185```
187And on `~sampel-sipnym`, assuming it's running `:ping` as well,
188we see `[%receiving 'heyya']`.
190This is an annoying way to invoke the app, though. The `[]` are
191mandatory, and optional arguments are hard. Let's make a
192generator, `+send` to make everything nicer. Since it's specific
193to `:ping`, let's put it in `/gen/ping/send.hoon`:
195```
196:- %say
197|= {^ {{to/@p message/?($~ {text/@t $~})} $~}
198[%ping-message to ?~(message 'howdy' text.message)]
199```
201A couple of new things here. Firstly, `message/?($~ {text=@t $~})`
202should be read as "the message is either null or a pair of text
203and null". Generator argument lists are always null-terminated,
204which makes it convenient to accept lists in tail position (which
205are particularly annoying without generators). `?(a b)` is the
206irregular form of `$?(a b)`, which is a union type. Thus, `?(a
207b)` means the type of anything that's in either `a` or `b`.
208Thus, in our case, `?(~ [text=@t ~])` is either null or a pair of
209text and null.
211Secondly, `?~(a b c)` is a rune which means "if the `a` is null,
212do `b`, else `c`". It is roughly equivalent to `?:(=(~ a) b c)`.
214<blockquote class="blockquote">
215`?~(a b c)` is actually equivalent to `?:=(?=(~ a) b c)`,
216which, although identical at run time, is subtly different at
217compile time. Specifically, using `?=` rather than `=` means
218that we're checking whether `a` is in the *type* of `~`, and so
219the compiler knows that in the `b` case `a` is null, and in the
220`c` case `a` is not null. Since `=` is purely a runtime value
221check with no type implications, the compiler doesn't gain any
222information.
224At any rate, this is the reason why we can refer to
225`text.message` in the `c` clause. The compiler knows that
226`message` is not null, so it must have `text` within it. If
227you used `?:(=(~ message) 'howdy' text.message)` the compiler
228would complain that it doesn't know whether `message` has
229`text` within it.
230</blockquote>
232This is run as follows:
234```
235~fintud-macrep:dojo> :ping|send ~sampel-sipnym
236>=
237~fintud-macrep:dojo> :ping|send ~sampel-sipnym 'how do you do'
238>=
239```
241Which causes `~sampel-sipnym` to print `[%receiving 'howdy']` and
242`[%receiving 'how do you do']`.
244**Exercises**:
246- Create a generator for `:sum` from [State](state) so that
247 you can run `:sum|add 5` to add numbers to it.
249- Create a generator for `:click` from [Web Apps](web-apps) so
250 that you can run `:click|poke` to poke it.