on master help sign in
Merge pull request #878 from urbit/dk-gall

update gall doc for static gall arms

commit1242c395875e379dd2b1710ccfbb2b6b26f809f4 authorDavid Kerschner <david@tlon.io> dateThu 25 Jun 2020, 14:27 (6 years ago) parent7b55239fadaf4f
2 files changed+76−243
tutorials/hoon/async-monad.md−215 tutorials/hoon/gall.md+76−28
tutorials/hoon/async-monad.mddeleted−215
@@ -1,215 +0,0 @@
1−+++
2−title = "2.7.2 Gall: Async Monad"
3−weight = 37
4−template = "doc.html"
5−+++
6−
7−Gall apps can be difficult to understand and simple ones often don't need the full complexity available when writing standard Gall apps. To that end, the `tapp` library implements the "async monad" which is more accurately described as a transitional IO monad. If you're not familiar with monads, don't worry as that understanding will not be necessary to use the `tapp` library.
8−
9−The following code is from `app/example-tapp-fetch.hoon` Much of this file should already be pretty readable, particularly given the comments in it, so we'll focus here on specific parts of the file rather than a full walkthrough. This app is intended to get the top comment of the top ten stories on Hacker News.
10−
11−```hoon
12−:: Little app to demonstrate the structure of programs written with the
13−:: transaction monad.
14−::
15−:: Fetches the top comment of each of the top 10 stories from Hacker News
16−::
17−/+ tapp, stdio
18−::
19−:: Preamble
20−::
21−=>
22− |%
23− +$ state
24− $: top-comments=(list tape)
25− ==
26− +$ peek-data _!!
27− +$ in-poke-data [%noun =cord]
28− +$ out-poke-data ~
29− +$ in-peer-data ~
30− +$ out-peer-data
31− $% [%comments (list tape)]
32− ==
33− ++ tapp (^tapp state peek-data in-poke-data out-poke-data in-peer-data out-peer-data)
34− ++ stdio (^stdio out-poke-data out-peer-data)
35− --
36−=>
37− |%
38− :: Helper function to print a comment
39− ::
40− ++ comment-to-tang
41− |= =tape
42− ^- tang
43− %+ welp
44− %+ turn (rip 10 (crip tape))
45− |= line=cord
46− leaf+(trip line)
47− [leaf+""]~
48− ::
49− :: All the URLs we fetch from
50− ::
51− ++ urls
52− =/ base "https://hacker-news.firebaseio.com/v0/"
53− :* top-stories=(weld base "topstories.json")
54− item=|=(item=@ud `tape`:(welp base "item/" +>:(scow %ui item) ".json"))
55− ==
56− --
57−=, async=async:tapp
58−=, tapp-async=tapp-async:tapp
59−=, stdio
60−::
61−:: The app
62−::
63−%- create-tapp-poke-peer-take:tapp
64−^- tapp-core-poke-peer-take:tapp
65−|_ [=bowl:gall state]
66−::
67−:: Main function
68−::
69−++ handle-poke
70− |= =in-poke-data
71− =/ m tapp-async
72− ^- form:m
73− ::
74− :: If requested to print, just print what we have in our state
75− ::
76− ?: =(cord.in-poke-data 'print')
77− ~& 'drumroll please...'
78− ;< now=@da bind:m get-time
79− ;< ~ bind:m (wait (add now ~s3))
80− ~& 'Top comments:'
81− %- (slog (zing (turn top-comments comment-to-tang)))
82− (pure:m top-comments)
83− ?: =(cord.in-poke-data 'poll')
84− ;< ~ bind:m (wait-effect (add now.bowl ~s15))
85− (pure:m top-comments)
86− ::
87− :: Otherwise, fetch the top HN stories
88− ::
89− =. top-comments ~
90− ::
91− :: If this whole thing takes more than 15 seconds, cancel it
92− ::
93− %+ (set-timeout _top-comments) (add now.bowl ~s15)
94− ;< =top-stories=json bind:m (fetch-json top-stories:urls)
95− =/ top-stories=(list @ud)
96− ((ar ni):dejs:format top-stories-json)
97− ::
98− :: Loop through the first 5 stories
99− ::
100− =. top-stories (scag 5 top-stories)
101− |- ^- form:m
102− =* loop $
103− ::
104− :: If done, tell subscribers and print the results
105− ::
106− ?~ top-stories
107− ;< ~ bind:m (give-result /comments %comments top-comments)
108− (handle-poke %noun 'print')
109− ::
110− :: Else, fetch the story info
111− ::
112− ~& "fetching item #{+>:(scow %ui i.top-stories)}"
113− ;< =story-info=json bind:m (fetch-json (item:urls i.top-stories))
114− =/ story-comments=(unit (list @ud))
115− ((ot kids+(ar ni) ~):dejs-soft:format story-info-json)
116− ::
117− :: If no comments, say so
118− ::
119− ?: |(?=(~ story-comments) ?=(~ u.story-comments))
120− =. top-comments ["<no top comment>" top-comments]
121− loop(top-stories t.top-stories)
122− ::
123− :: Else, fetch comment info
124− ::
125− ;< =comment-info=json bind:m (fetch-json (item:urls i.u.story-comments))
126− =/ comment-text=(unit tape)
127− ((ot text+sa ~):dejs-soft:format comment-info-json)
128− ::
129− :: If no text (eg comment deleted), record that
130− ::
131− ?~ comment-text
132− =. top-comments ["<top comment has no text>" top-comments]
133− loop(top-stories t.top-stories)
134− ::
135− :: Else, add text to state
136− ::
137− =. top-comments [u.comment-text top-comments]
138− loop(top-stories t.top-stories)
139−::
140−++ handle-peer
141− |= =path
142− =/ m tapp-async
143− ^- form:m
144− ~& [%tapp-fetch-take-peer path]
145− (pure:m top-comments)
146−::
147−++ handle-take
148− |= =sign:tapp
149− =/ m tapp-async
150− ^- form:m
151− :: ignore %poke/peer acknowledgements
152− ::
153− ?. ?=(%wake -.sign)
154− (pure:m top-comments)
155− ;< =state bind:m (handle-poke %noun 'fetch')
156− =. top-comments state
157− (pure:m top-comments)
158−--
159−```
160−
161−As indicated by the comments, `handle-poke` is the main [arm](/docs/glossary/arm/) of our Gall app. This is equivalent to the `poke` arms in a standard Gall app, but will not differentiate by mark. Let's look at the first part of this [gate](/docs/glossary/gate/).
162−
163−```hoon
164−++ handle-poke
165− |= =in-poke-data
166− =/ m tapp-async
167− ^- form:m
168− ::
169− :: If requested to print, just print what we have in our state
170− ::
171− ?: =(cord.in-poke-data 'print')
172− ~& 'drumroll please...'
173− ;< now=@da bind:m get-time
174− ;< ~ bind:m (wait (add now ~s3))
175− ~& 'Top comments:'
176− %- (slog (zing (turn top-comments comment-to-tang)))
177− (pure:m top-comments)
178− ?: =(cord.in-poke-data 'poll')
179− ;< ~ bind:m (wait-effect (add now.bowl ~s15))
180− (pure:m top-comments)
181−```
182−
183−The first part of this gate inspects the `in-poke-data` structure, specifically the `cord` to determine what request is being made. Here the request is to just print the data that we already have.
184−
185−The most important part to see here is the `;<` rune. If you're familiar with monadic bind, this is what this rune is. If you're not, don't worry! While we wont be discussing the implementation details of this rune, it's quite simple to use.
186−
187−`;<` has four children: `A`, `B`, `C` and `D`.
188−
189−`A` is a mold that describes the type produced by `C`.
190−
191−`B` is the monadic bind function, `bind:tapp-async`.
192−
193−`C` is some code we want to run and `D` is code to run after the `C` has completed successfully. If `C` fails, then the entire `;<` call will fail. This will allow us to chain a series of calls which may take a long time to complete, such as http requests, together and proceed when they actually complete.
194−
195−`D` must produce the type defined by `B`, `tapp-async` or `(async ,state)`. In the final leg of the `;<` series in this app you'll see the calls to `pure:m` which is used to wrap `top-comments` in an `async`.
196−
197−In a number of cases `D` here is going to be another `;<` rune. This is how you can chain these together to perform a lot of asynchronous actions that need to all complete.
198−
199−```hoon
200− ::
201− :: Otherwise, fetch the top HN stories
202− ::
203− =. top-comments ~
204− ::
205− :: If this whole thing takes more than 15 seconds, cancel it
206− ::
207− %+ (set-timeout _top-comments) (add now.bowl ~s15)
208− ;< =top-stories=json bind:m (fetch-json top-stories:urls)
209− =/ top-stories=(list @ud)
210− ((ar ni):dejs:format top-stories-json)
211−```
212−
213−Continuing on, here is a use of `fetch-json` with the `;<` that is an example worth reading. `fetch-json` takes a `tape` and produces a `form:(async ,json)` which can be processed to obtain the data we want.
214−
215−Armed with an understanding of `;<`, the rest of `handle-poke` should be quite readable though you may need to consult the `dejs-soft:format` [core](/docs/glossary/core/) to get a firm grasp on how JSON parsing works. Hopefully, you now have an understanding of the basic use of the `tapp` library. This style of Gall app can result in code that, while potentially less flexible, is easier to read and reason about than a traditional Gall app.
tutorials/hoon/gall.md+76−28
@@ -5,10 +5,10 @@ template = "doc.html"
55aliases = ["/docs/learn/hoon/hoon-tutorial/gall/"]
66+++
77
8−> Note: This guide is outdated. For an updated explanation of Gall, take a look at [/tutorial/arvo/gall](/docs/tutorials/arvo/gall)
9−
108Gall is the Arvo vane responsible for handling user space applications. When writing a Gall application there are several things you will need to understand.
119
10+For another explanation of Gall, take a look at [/tutorial/arvo/gall](/docs/tutorials/arvo/gall)
11+
1212## bowl and moves
1313
1414The [core](/docs/glossary/core/) of a gall app is a [door](/docs/glossary/door/) which has two parts of its subject, the first a `bowl:gall` which contains a lot of standard things used by gall apps, the second a type containing app state information.
@@ -34,50 +34,98 @@ Each `card` is a pair of a tag and a [noun](/docs/glossary/noun/). The tag indic
3434
3535Gall applications can have a number of arms that get called depending on the information they are sent.
3636
37−### ++prep
37+This arm is called once when the agent is started. It has no input and
38+lets you perform any initial IO.
3839
39−`++prep` is the arm that is called when an application is first started or when it's updated. This arm should be a [gate](/docs/glossary/gate/) that takes a `unit` of a noun and provides a way, if necessary, to make any changes to the application's data required by an upgrade. As a reminder, a `unit` is a type that may contain another type or it might contain `~`. They are used when there may or may not be some data available.
40+### +on-init
4041
41−Often when developing an application you will not initially care about the data. Here is a sample `++prep` arm that will simply throw away the previous application state.
42+This arm is called when the app is initially started.
4243
43−```hoon
44−++ prep
45− |= a=(unit *)
46− `(quip move _+>.$)`[~ +>.$]
47−```
44+### +on-save
45+
46+This arm is called immediately before the agent is upgraded. It
47+packages the permament state of the agent in a `vase` for the next version
48+of the agent. Unlike most handlers, this cannot produce effects.
49+
50+### +on-load
51+
52+This arm is called immediately after the agent is upgraded. It receives
53+a `vase` of the state of the previously running version of the agent,
54+which allows it to cleanly upgrade from the old agent.
55+
56+### +on-poke
57+
58+This arm is called when the agent is "poked". The input is a `cage`, so
59+it's a pair of a mark and a dynamic `vase`.
60+
61+### +on-watch
4862
49−### ++poke
63+This arm is called when a program wants to subscribe to the agent on a
64+particular `path`. The agent may or may not need to perform setup steps
65+to intialize the subscription. It may produce a `%give`
66+`%subscription-result` to the subscriber to get it up to date, but after
67+this event is complete, it cannot give further updates to a specific
68+subscriber. It must give all further updates to all subscribers on a
69+specific `path`.
5070
51−`++poke` is one of the primary arms used when building a Gall application. A `poke` is often a request to perform some operation that the application was designed for. These are requests from outside the application. `++poke` will get called with the raw noun data which can then be inspected to perform the requested actions with.
71+If this arm crashes, then the subscription is immediately terminated.
72+More specifcally, it never started -- the subscriber will receive a
73+negative `%watch-ack`. You may also produce an explicit `%kick` to
74+close the subscription without crashing -- for example, you could
75+produce a single update followed by a `%kick`.
5276
53−Many arms, `++poke` included, have variants that end in the name of a mark e.g. `++poke-noun`. Which one gets called will be based on which mark was used to create the poke. Gall will attempt to use the most specific arm it can find, eventually falling back to `++poke` if no matching mark arm is found. Use of these mark arms, however, is now discouraged.
77+### +on-leave
5478
55−### ++coup
79+This arm is called when a program becomes unsubscribed to you.
80+Subscriptions may close because the subscriber intentionally
81+unsubscribed, but they also could be closed by an intermediary. For
82+example, if a subscription is from another ship which is currently
83+unreachable, Ames may choose to close the subscription to avoid queueing
84+updates indefinitely. If the program crashes while processing an
85+update, this may also generate an unsubscription. You should consider
86+subscriptions to be closable at any time.
5687
57−`++coup` is a response handler for any pokes our application sent out.
88+### +on-peek
5889
59−### ++peer
90+This arm is called when a program reads from the agent's "scry"
91+namespace, which should be referentially transparent. Unlike most
92+handlers, this cannot perform IO, and it cannot change the state. All
93+it can do is produce a piece of data to the caller, or not.
6094
61−`++peer` is used to handle subscriptions. When something subscribes to your application this arm will run. The something could be another Gall application on your ship or another ship or a tile from landscape or anything else that is able to communicate with Gall. Your outgoing subscriptions will not be tracked automatically but incoming ones will live in `sup.bowl` so that you can send information to them when required.
95+If this arm produces `[~ ~ data]`, then `data` is the value at the the
96+given `path`. If it produces `[~ ~]`, then there is no data at the given
97+`path` and never will be. If it produces `~`, then we don't know yet
98+whether there is or will be data at the given `path`.
6299
63−### ++pull
100+### +on-agent
64101
65−`++pull` is run when someone unsubscribes from your application, this is used to run any changes needed for your application when they unsubscribed. Their removal from `sup.bowl` is handled for you.
102+This arm is called to handle responses to `%give` moves to other agents.
103+It will be one of the following types of response:
66104
67−### ++quit
105+- `%poke-ack`: acknowledgment (positive or negative) of a poke. If the
106+ value is `~`, then the poke succeeded. If the value is `[~ tang]`,
107+ then the poke failed, and a printable explanation (e.g. a stack trace)
108+ is given in the `tang`.
68109
69−`++quit` gets called when a subscription is dropped. Often you will just want to resubscribe when this happens.
110+- `%watch-ack`: acknowledgment (positive or negative) of a subscription.
111+ If negative, the subscription is already ended (technically, it never
112+ started).
70113
71−### ++reap
114+- `%fact`: update from the publisher.
72115
73−`++reap` is to `++peer` as `++coup` is to `++poke`, that is to say it's the arm that gets run in acknowledgment of outgoing subscription requests.
116+- `%kick`: notification that the subscription has ended.
74117
75−### ++diff
118+### +on-arvo
76119
77−`++diff` gets called when an application you are subscribed to has an update. It could either be an entirely new set of data for that application or an update to existing data.
120+This arm is called to handle responses to `%pass` `move`s to vanes. The
121+list of possible responses from the system is statically defined in
122+sys/zuse.hoon (grep for `+ sign-arvo`).
78123
79−### ++sigh
124+### +on-fail
80125
81−`++sigh` gets called when Eyre has a response to an http request made by our application.
126+If an error happens in `+on-poke`, the crash report goes into the
127+`%poke-ack` response. Similarly, if an error happens in
128+`+on-subscription`, the crash report goes into the `%watch-ack`
129+response. If a crash happens in any of the other handlers, the report
130+is passed into this arm.
82131
83−Let's take a look in the next section at an example Gall app.