Fundamentals
Introduction
A thread is like a transient gall agent. Unlike an agent, it can end and it can fail. The primary uses for threads are:
Complex IO, like making a bunch of external API calls where each call depends on the last. Doing this in an agent significantly increases its complexity and the risk of a mishandled intermediary state corrupting permanent state. If you spin the IO out into a thread, your agent only has to make one call to the thread and receive one response.
Testing - threads are very useful for writing complex tests for your agents.
Threads are managed by the Gall agent called %spider
. You can poke %spider
directly, or you can pass a task to the Khan thread runner vane and let it handle %spider
for you.
Threads can be run from a file in the /ted
directory, or an "inline thread" can be passed directly to Khan from within your agent.
Thread file location
Thread files live in the ted
directory of each desk. For example, in a desk named %sandbox
:
%sandbox
├──app
├──gen
├──lib
├──mar
├──sur
└──ted <-
├──foo
│ └──bar.hoon
└──baz.hoon
From the dojo, ted/baz.hoon
can be run with -sandbox!baz
, and ted/foo/bar.hoon
with -sandbox!foo-bar
. Threads in the %base
desk can just be run like -foo
, but all others must have the format -desk!thread
.
Libraries
There are two libraries that may be relevant:
/sur/spider/hoon
- this contains a few simple structures used by%spider
. It's only relevant if you're running thread files by poking%spider
directly. If you're running them by passing a task to Khan as is typical, it can be ignored./lib/strandio/hoon
- this contains a large collection of ready-made functions for use in threads. You'll likely use many of these when you write threads, so it's very useful.
Thread definition
A thread is defined as a $-(vase shed:khan)
. That is, a gate that takes a $vase
and produces a $shed:khan
. A $shed:khan
is the form
of a strand that produces a $vase
. This is a little confusing and we'll look at each part in detail later. For now, note that the thread doesn't just produce a result, it actually produces a strand that takes input and produces output from which a result can be extracted. It works something like this:
This is because threads typically do a bunch of I/O so it can't just immediately produce a result and end. Instead the strand will get some input, produce output, get some new input, produce new output, and so forth, until they eventually produce a %done
with the actual final result.
Strands
Strands are the building blocks of threads. A thread will typically compose multiple strands.
A strand is a function of $strand-input:rand
to $output:strand:rand
, the latter of which is a +strand-output-raw:rand
initialized with a particular mold:
+$ strand-input
$+ strand-input
[=bowl in=(unit input)]
++ strand-output-raw
|* a=mold
$+ strand-output-raw
$~ [~ %done *a]
$: cards=(list card)
$= next
$% [%wait ~]
[%skip ~]
[%cont self=(strand-form-raw a)]
[%fail err=error]
[%done value=a]
==
==
At this stage you don't need to know the nitty-gritty details but it's helpful to have a quick look through the +rand
arm in lull.hoon
. We'll discuss these things in more detail later.
A strand is a core that has three important arms:
+form
- the mold of the strand+pure
- produces a strand that does nothing except return a value+bind
- monadic bind, likethen
in javascript promises
We'll discuss each of these arms later.
A strand must be specialised to produce a particular type like (strand:rand ,<type>)
. As previously mentioned, a thread
produces a vase
so is specialised like (strand:rand ,vase)
. Within your thread you'll likely compose multiple strands which produce different types like (strand:rand ,@ud)
, (strand:rand ,[path cage])
, etc, but the thread itself will always come back to a (strand:rand ,vase)
.
Strands are conventionally given the face m
like:
=/ m (strand:rand ,vase)
...
NOTE: a comma prefix as in ,vase
is the irregular form of ^:
ketcol which produces a gate that returns the sample value if it's of the correct type, but crashes otherwise.
Form and Pure
+form
+form
The +form
arm is the mold of the strand, suitable for casting. The two other arms produce +form
s so you'll cast everything to this like:
=/ m (strand:rand ,@ud)
^- form:m
...
+pure
+pure
Pure produces a strand that does nothing except return a value. So, (pure:(strand:rand ,@tas) %foo)
is a strand that produces %foo
without doing any IO.
We'll cover +bind
later.
A trivial thread
|= arg=vase
=/ m (strand:rand ,vase)
^- form:m
(pure:m arg)
The above code is a simple thread that just returns its argument, and it's a good boilerplate to start from.
Save the above code as a file in ted/mythread.hoon
and |commit
it. Run it with -mythread 'foo'
, you should see the following:
> -mythread 'foo'
[~ 'foo']
NOTE: The dojo wraps arguments in a unit so that's why it's [~ 'foo']
rather than just 'foo'
.
Analysis
We'll go through it line-by line.
|= arg=vase
We create a gate that takes a vase, the first part of the previously mentioned thread definition.
=/ m (strand:rand ,vase)
Inside the gate we create our +strand
specialised to produce a $vase
and give it the canonical face m
.
^- form:m
We cast the output to form
- the mold of the strand we created.
(pure:m arg)
Finally we call pure
with the gate input arg
as its argument. Since arg
is a vase
it will return the +form
of a +strand
which produces a $vase
. Thus we've created a thread in accordance with its type definition.
Inline Threads
While you can store threads as files in the /ted
directory, you can also include threads directly in your Gall agent code and ask the Khan vane to run them.
While a stand-alone thread file is expected to be a $-(vase shed:khan)
, an inline thread is just a $shed:khan
. That is, it doesn't take any initial argument argument. Instead, you can simply reference any data and functions available in its subject in the Gall agent.
Here's how a trivial inline thread that'll just return 123
, a number pinned previously in the Gall agent, might look:
=/ dat=@ud 123
=/ =shed:khan
=/ m (strand:rand ,vase)
^- form:m
(pure:m !>(dat))
The $shed
can then be passed to Kahn in card:
[%pass /thread %arvo %k %lard %mydesk shed]~
The result will come back into the ++on-arvo
arm of the Gall agent in an %arow
gift.
Next we'll look at the third arm of a strand: +bind
.
Last updated