on 6d45ede help sign in
urbit.org/docs/arvo/generators.md
178 lines 6.7 kB history raw
1---
2navhome: '/docs'
3next: True
4sort: 8
5title: Using Generators with Apps
6---
8# Generators
10Up until now we've poked apps directly. This requires the user to
11specify the mark, and it requires the app to accept the arguments in a
12way that's convenient for users to input. This is the "plumbing" way to
13interact with apps. Generators are the "porcelain" layer. This is why
14when you run a command like `+ls` or `|merge`, there are no marks in
15sight.
17We've used generators before, back in [Basic
18Operation](/docs/using/admin). At that point, we just used the
19generators to produce values -- we didn't pipe their results into apps.
20In the dojo cast, the role of a generator is to take a list of arguments
21and produce a value, which is often, though not always, piped into an
22app. Generators are pure, stateless functions, and they cannot send
23moves.
25`+ls` and `+cat` are commonly-used generators that produce a directory
26listing and print out a file, respectively. These are useful in
27themselves, so we generally don't pipe the results into an app.
29Another generator is `+hood/merge`. Try running `+hood/merge` with the
30normal arguments for `|merge`:
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]
37This didn't run any merge, it just constructed the command that, if sent
38to `:hood`, would run it. Note first that, even though it's not printed,
39this value has mark `kiln-merge`. Also, the merge strategy `gem=%auto`
40was added automatically. Optional arguments are straightforward with
41generators.
43In general, generators are prepended by either `+` or `|`. The general
44form is `+generator`, but often generators are created specifically to
45work with a single app, they're usually placed in a subdirectory of gen
46and run with `|`. Thus, if `my-generator` generator is made for use with
47`:my-app`, then `my-generator` is put in
48`/gen/my-app/my-generator.hoon`, and can be run directly with
49`+my-app/my-generator <args>`. If you want to pipe the result into
50`:my-app`, then run `:my-app +my-app/my-generator <args>`. Because this
51pattern is so common, this can be abbreviated to
52`:my-app|my-generator <args>`. Because most built-in commands are
53generators for the `:hood` app, `:hood|generator <args>` can be
54shortened to `|generator <args>`.
56Let's write a generator for a modified version of `:examples-pong` from
57the chapter on [Network Messages](/docs/arvo/system/network). Recall
58that `:examples-pong` takes an urbit address, which is of mark `urbit`,
59and sends that urbit the message `'howdy'`. First--without a
60generator--let's make `:examples-ping` that does the same, except that
61it lets the user optionally specify the message as well.
63We'll need a new mark for our arguments. Let's call it
64`examples-ping-message`.
66> For app-specific marks, it's good style to prefix the name of the mark
67> with the name of the app. Since many apps have several such marks,
68> subdirectories in `/mar` are rendered as `-`, so that `ping-message`
69> is written in (/mar/examples/ping/message.hoon).
71 ::
72 :::: /hoon/message/ping/examples/app
73 ::
74 /? 314
75 |_ {to/@p message/@t}
76 ++ grab
77 |%
78 ++ noun {@p @t}
79 --
80 --
82The app can easily be modified to use this (`/app/examples/ping.hoon`):
84 :: Allows one ship to ping another with a string of text
85 ::
86 :::: /hoon/ping/examples/app
87 ::
88 /? 314
89 |%
90 ++ move {bone term wire *}
91 --
92 !:
93 |_ {bowl state/$~}
94 ::
95 ++ poke-examples-ping-message
96 |= {to/@p message/@t}
97 ~& 'sent'
98 ^- {(list move) _+>.$}
99 :- ^- (list move)
100 :~ `move`[ost %poke /sending [to dap] %atom message]
101 ==
102 +>.$
103 ::
104 ++ poke-atom
105 |= arg/@
106 ~& 'received'
107 ^- {(list move) _+>.$}
108 ::
109 ~& [%receiving (@t arg)]
110 [~ +>.$]
111 ::
112 ++ coup |=(* `+>)
113 --
115Now we can run this with:
117 ~fintud-macrep:dojo> |start %examples-ping
118 >=
119 ~fintud-macrep:dojo> :examples-ping &examples-ping-message [~sampel-sipnym 'heyya']
120 >=
122And on `~sampel-sipnym`, assuming it's running `:examples-ping` as well,
123we see `[%receiving 'heyya']`.
125This is an annoying way to invoke the app, though. The `[]` are
126mandatory, and optional arguments are hard. Let's make a generator,
127`+examples-send` to make everything nicer. Since it's specific to
128`:examples-ping`, let's put it in `/gen/examples/ping/send.hoon`:
130 :- %say
131 |= {^ {to/@p message/?($~ {text/@t $~})} $~}
132 [%examples-ping-message to ?~(message 'howdy' text.message)]
134A couple of new things here. Firstly, `message/?($~ {text/@t $~})`
135should be read as "the message is either null or a pair of text and
136null". Generator argument lists are always null-terminated, which makes
137it convenient to accept lists in tail position (which are particularly
138annoying without generators). `?(a b)` is the irregular form of
139`$?(a b)`, which is a union type. Thus, `?(a b)` means the type of
140anything that's in either `a` or `b`. Thus, in our case,
141`?(~ [text=@t ~])` is either null or a pair of text and null.
143Secondly, `?~(a b c)` is a rune which means "if the `a` is null, do `b`,
144else `c`". It is roughly equivalent to `?:(=(~ a) b c)`.
146> `?~(a b c)` is actually equivalent to `?:=(?=(~ a) b c)`, which,
147> although identical at run time, is subtly different at compile time.
148> Specifically, using `?=` rather than `=` means that we're checking
149> whether `a` is in the *type* of `~`, and so the compiler knows that in
150> the `b` case `a` is null, and in the `c` case `a` is not null. Since
151> `=` is purely a runtime value check with no type implications, the
152> compiler doesn't gain any information.
154> At any rate, this is the reason why we can refer to `text.message` in
155> the `c` clause. The compiler knows that `message` is not null, so it
156> must have `text` within it. If you used
157> `?:(=(~ message) 'howdy' text.message)` the compiler would complain
158> that it doesn't know whether `message` has `text` within it.
160This is run as follows:
162 ~fintud-macrep:dojo> :examples-ping|send ~sampel-sipnym
163 >=
164 ~fintud-macrep:dojo> :examples-ping|send ~sampel-sipnym 'how do you do'
165 >=
167Which causes `~sampel-sipnym` to print `[%receiving 'howdy']` and
168`[%receiving 'how do you do']`.
170**Exercises**:
172- Create a generator for `:examples-sum` from
173 [State](/docs/arvo/state) so that you can run `:examples-sum|add 5`
174 to add numbers to it.
176- Create a generator for `:examples-click` from [Web
177 Apps](/docs/arvo/web-apps) so that you can run
178 `:examples-click|poke` to poke it.