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 /home/app/egg-timer.hoon After committing: |start %egg-timer and poke with :egg-timer ~s10

/+  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 sys/zuse.hoon

|%
+$  card  card:agent:gall
--

The core here defines only one type: card. A 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(|_) is:

=bowl:gall

You can find the full definition of bowl in 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:

We give bowl:gall the face bowl. Additionally, we use +* to create two structures for future use:

+*  this  .
    def   ~(. (default-agent this %.n) bowl)

this is our current context(the door). def uses 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)]~

The ++on-poke arm evaluates to a gate which takes a sample [mark vase]. mark is a type specification and vase is a cell of [specification value] Note: since ++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 vase with =/ t !<(@dr vase) @dr is an aura for a 128-bit relative date. Here are a few examples.

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 %wait request. Behn will start a timer for us, and will respond along our wire, /egg-timer.

now.bowl is the current time of type @da, and t was declared as @dr. Because they are both atoms, we can add now and t to produce an atom that is t units of time into the future from now. That produced atom can be interpreted as a @da. 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 arm.

++  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 wire and 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. A 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 sign-arvo is [%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, [~ 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
--

Finally, we define the remaining 8 unused but required Gall Agent armsas instances of the default arms provided in /lib/default-agent.hoon.