2.7.1 Gall Walkthrough: Egg Timer
The Arvo operating system is divided up into modules called vanes. Gall is the vane responsible for providing an API for building stateful applications in Urbit. Gall is a complex system that takes a lot of getting used to, so let's dip our toes in with a simple egg-timer app.
The following code sample should be saved as
|start %egg-timer and poke with
/+ default-agent |% +$ card card:agent:gall -- :: ^- agent:gall |_ =bowl:gall +* this . def ~(. (default-agent this %.n) bowl) :: ++ on-poke |= [=mark =vase] ^- (quip card _this) =/ t !<(@dr vase) :_ this [%pass /egg-timer %arvo %b %wait (add now.bowl t)]~ :: ++ on-arvo ^+ on-arvo:*agent:gall |= [=wire =sign-arvo] ^- (quip card _this) ~& "Timer went off!" [~ this] :: ++ on-init on-init:def ++ on-save on-save:def ++ on-load on-load:def ++ on-watch on-watch:def ++ on-leave on-leave:def ++ on-peek on-peek:def ++ on-agent on-agent:def ++ on-fail on-fail:def --
The first thing to notice is that we are creating a core (
|%) and a door (
|_). This is a typical style for Gall programming wherein types are defined in the first core and your application is defined in the following door.
Our door will have type
agent:gall as specified in
|% +$ card card:agent:gall --
The core here defines only one type:
card is a request to Arvo to do something for us. In this case, the only card used will be a
%wait request to Behn which we'll discuss later.
It's important to note that the name
card is technically arbitrary. By convention, however, you will see it called
card practically everywhere.
The sample of the door(
You can find the full definition of
sys/zuse.hoon, but for now it's enough to know that includes contextual data used by Gall with particular faces. Below are some important such faces:
ourThe ship this code is running on
srcThe ship the current event originated from
nowThe current time
bowl:gall the face
Additionally, we use
+* to create two structures for future use:
+* this . def ~(. (default-agent this %.n) bowl)
this is our current context(the door).
default-agent to produce a standard
gall:agent door, from which we will instance default definitions for our unused arms.
All Gall agents have a fixed set of 10 arms. In this trivial case, we will specify only two and use the default definitions from
default-agent for the remaining arms.
++ on-poke |= [=mark =vase] ^- (quip card _this) =/ t !<(@dr vase) :_ this [%pass /egg-timer %arvo %b %wait (add now.bowl t)]~
++on-poke arm evaluates to a gate which takes a sample
mark is a type specification and
vase is a cell of
++on-poke may take many different samples, it would be prudent to add a
%noun mark check here.
In this example, we assume that our sample will contain a
@dr and pull it from the
=/ t !<(@dr vase)
@dr is an aura for a 128-bit relative date. Here are a few examples.
~m13s3713 minutes, 37 seconds
As a matter of good type hygiene, we explicitly cast the output of this gate with
^- to ensure we are producing the correct structure for Gall to handle.
Our specification is
(quip card _this) which produces a type,
[(list card) _this]. Note that
_this is irregular form of
$_(this) which normalizes to the type of
this, our door.
Next we use the
:_ rune which is the inverted form of
:-, the cell construction rune.
:_ this [%pass /egg-timer %arvo %b %wait (add now.bowl t)]~
Our card should follow the basic form
[%pass /my/wire a-note]
In this case, our card will ask Arvo to
%pass a note along the wire
/egg-timer to Behn(
%b) containing a
Behn will start a timer for us, and will respond along our wire,
now.bowl is the current time of type
t was declared as
@dr. Because they are both atoms, we can add
t to produce an atom that is
t units of time into the future from
now. That produced atom can be interpreted as a
The calculated time will be passed via Arvo to Behn with instruction to
%wait @da, i.e., to set a timer.
That's all for our
++on-poke arm. But what about when the timer goes off? Behn will create a
gift, a similar construct to our
card which will be dispatched back to us via Gall in the
++ on-arvo ^+ on-arvo:*agent:gall |= [=wire =sign-arvo] ^- (quip card _this) ~& "Timer went off!" [~ this]
++on-arvo evaluates to a gate whose sample is a cell of
sign-arvo here given default faces. The syntax of
=wire is a shortcut for
wire=wire; it's a common pattern to shadow the name of a type when you only have one instance of the type and are not going to refer to the type itself.
wire is an alias for a path. Our
wire will be the path we provided in our
++on-poke card. If we needed to do something particular depending on which request triggered this Arvo card, we could use the wire to do so.
Note: since multiple vanes can send many sorts of cards via
++on-arvo, it would be prudent to check if
[%b %wake *]. Since our trivial example assumes the card is
%wake from Behn, we will forgo that better practice here.
Next we have the same cast we used in
++on-poke to make sure we produce the correct type for Gall.
(quip card _this) resolves to a type
[(list card) _this]
~& "Timer went off!"
~& is the debugging printf rune. Here we're using it to output a message. We could, however, modify this line of code to do any other computation when
++on-arvo is called.
We need to produce a noun of type
(quip card _this) i.e.,
[(list card) _this].
In this case, we will not produce any card or change application state. Therefore we simply produce a cell of a null list and our original context,
++ on-init on-init:def ++ on-save on-save:def ++ on-load on-load:def ++ on-watch on-watch:def ++ on-leave on-leave:def ++ on-peek on-peek:def ++ on-agent on-agent:def ++ on-fail on-fail:def --
Finally, we define the remaining 8 unused but required Gall Agent armsas instances of the default arms provided in