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