@@ -0,0 +1,215 @@
2+title = "2.7.2 Gall: Async Monad"
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 that understand will not be necessary to using the `tapp` library.
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.
12+:: Little app to demonstrate the structure of programs written with the
15+:: Fetches the top comment of each of the top 10 stories from Hacker News
24+ $: top-comments=(list tape)
27+ +$ in-poke-data [%noun =cord]
31+ $% [%comments (list tape)]
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)
38+ :: Helper function to print a comment
44+ %+ turn (rip 10 (crip tape))
49+ :: All the URLs we fetch from
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"))
58+=, tapp-async=tapp-async:tapp
63+%- create-tapp-poke-peer-take:tapp
64+^- tapp-core-poke-peer-take:tapp
74+ :: If requested to print, just print what we have in our state
76+ ?: =(cord.in-poke-data 'print')
77+ ~& 'drumroll please...'
78+ ;< now=@da bind:m get-time
79+ ;< ~ bind:m (wait (add now ~s3))
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)
87+ :: Otherwise, fetch the top HN stories
91+ :: If this whole thing takes more than 15 seconds, cancel it
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)
98+ :: Loop through the first 5 stories
100+ =. top-stories (scag 5 top-stories)
104+ :: If done, tell subscribers and print the results
107+ ;< ~ bind:m (give-result /comments %comments top-comments)
108+ (handle-poke %noun 'print')
110+ :: Else, fetch the story info
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)
117+ :: If no comments, say so
119+ ?: |(?=(~ story-comments) ?=(~ u.story-comments))
120+ =. top-comments ["<no top comment>" top-comments]
121+ loop(top-stories t.top-stories)
123+ :: Else, fetch comment info
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)
129+ :: If no text (eg comment deleted), record that
132+ =. top-comments ["<top comment has no text>" top-comments]
133+ loop(top-stories t.top-stories)
135+ :: Else, add text to state
137+ =. top-comments [u.comment-text top-comments]
138+ loop(top-stories t.top-stories)
144+ ~& [%tapp-fetch-take-peer path]
145+ (pure:m top-comments)
151+ :: ignore %poke/peer acknowledgements
154+ (pure:m top-comments)
155+ ;< =state bind:m (handle-poke %noun 'fetch')
156+ =. top-comments state
157+ (pure:m top-comments)
161+As indicated by the comments, `handle-poke` is the main 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.
169+ :: If requested to print, just print what we have in our state
171+ ?: =(cord.in-poke-data 'print')
172+ ~& 'drumroll please...'
173+ ;< now=@da bind:m get-time
174+ ;< ~ bind:m (wait (add now ~s3))
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)
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.
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.
187+`;<` has four children: `A`, `B`, `C` and `D`.
189+`A` is a mold that describes the type produced by `C`.
191+`B` is the monadic bind function, `bind:tapp-async`.
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.
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`.
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.
201+ :: Otherwise, fetch the top HN stories
205+ :: If this whole thing takes more than 15 seconds, cancel it
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)
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 an `form:(async ,json)` which can be processed to obtained the data we want.
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 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.