`%gall`: Adding Functionality

Overview

Teaching: 30 min
Exercises: 15 min
Questions
  • How can I build a %gall app which operates on data?

Objectives
  • Manage internal %gall state.

  • Scry for needed information.

Generally speaking, the Urbit data flow model is reactive, meaning that rather than poll for updates periodically one subscribes to a data source which notifies all subscribers when a change occurs.

Arvo defines a number of standard operations for each vane. Notable among these are peeks, which grant read-only access to data, called a scry; and pokes, which accept moves and process them. Pokes actually alter the agent’s (and Arvo’s) state (rather than just retrieve information).

We are going to widen our view a little bit as well with this agent: we will not use the default arms but will define our own NOP defaults. This way you will be able to see what sort of information each arm processes.

/app/bravo.hoon:

/-  bravo
/+  default-agent, dbug
|%
+$  versioned-state
  $%  state-0
  ==
::
+$  state-0
  $:  [%0 hexes=(list @ux)]
  ==
::
+$  card  card:agent:gall
::
--
%-  agent:dbug
=|  state-0
=*  state  -
^-  agent:gall
=<
|_  bowl:gall
+*  this      .
    default   ~(. (default-agent this %|) bowl)
    main      ~(. +> bowl)
::
++  on-init
  ^-  (quip card _this)
  ~&  >  '%bravo initialized successfully'
  =.  state  [%0 *(list @ux)]
  `this
::
++  on-save
  ^-  vase
  !>(state)
::
++  on-load
  |=  old-state=vase
  ^-  (quip card _this)
  ~&  >  '%bravo recompiled successfully'
  `this(state !<(versioned-state old-state))
::
++  on-poke
  |=  [=mark =vase]
  ^-  (quip card _this)
  ?+    mark  (on-poke:default mark vase)
      %noun
    ?+    q.vase  (on-poke:default mark vase)
        %print-state
      ~&  >>  state
      ~&  >>>  bowl  `this
        [%print-pop @ux]
      ~&  >>  +>:vase  `this
    ==
    ::
      %bravo-action
    ~&  >  %bravo-action
    =^  cards  state
    (handle-action:main !<(action:bravo vase))
    [cards this]
  ==
::
++  on-watch
  |=  =path
  `this
::
++  on-leave
  |=  =path
  `this
::
++  on-peek
  |=  =path
  *(unit (unit cage))
::
++  on-agent
  |=  [wire sign:agent:gall]
  `this
::
++  on-arvo
  |=  [=wire =sign-arvo]
  `this
::
++  on-fail
  |=  [=term =tang]
  `this
--
|_  =bowl:gall
++  handle-action
  |=  =action:bravo
  ^-  (quip card _state)
  ?-    -.action
    ::
      %push
    =.  hexes.state  (weld hexes.state ~[value.action])
    ~&  >>  hexes.state
    :_  state
    ~[[%give %fact ~[/hexes] [%atom !>(hexes.state)]]]
    ::
      %pop
    =/  popped  (rear hexes.state)
    =.  hexes.state  (snip hexes.state)
    ~&  >>  hexes.state
    :_  state
    :~  [%give %fact ~[/hexes] [%atom !>(hexes.state)]]
        [%pass /print-pop %agent [our.bowl %charlie] %poke %noun !>([%print-pop popped])]
    ==
  ==
--

You should copy the structure file and mark file from %alfa and adapt them as appropriate for %bravo. This should be a matter of copying to the appropriate path and changing any internal references.

The structure file should accommodate the following actions:

:bravo &bravo-action push+0xacdc
:bravo &bravo-action pop+~

We will also accommodate external scrying into the agent through the ++on-peek arm. Once the above works correctly, you should add in an augmented ++on-peek arm:

++  on-peek
  |=  =path
  ^-  (unit (unit cage))
  ?+    path  (on-peek:default path)
      [%x %hexes ~]
    ``noun+!>(hexes)
  ==

This arm typically accepts two kinds of scries (called cares):

For this case, we only need to return data, so we will only support %gx scries. (We have nothing path-like in this agent.)

Scry results are directly accessible via .^ dotket operations at the Dojo prompt (and more generally to other agents). However, scries can only be performed locally—there are no remote scries as a security mechanism. Remote agent data must be formally requested via a poke and return.

.^((list @ux) %gx /=bravo=/hexes/noun)

units, cages, and vases, Oh My!

  • A unit allows us to distinguish “no result” from “zero result”. Since every atom in Hoon is an unsigned integer, this allows us to tell the difference between an operation that has no possible result and an operation that succeeded but returned ~ or 0. A unit can be trivially produced from any value by prefixing a tic mark \`.

  • A vase wraps a value in its type. A vase generally results from the type spear -:!>().

  • A cage is a marked vase; that is, a vase with additional information about its structure. A cage is more or less analogous to a file in a regular filesystem.

These bear the following relationship to a simple atom:

> !>(1)
[#t/@ud q=1]
> (vase !>(1))
[#t/@ud q=1]
> (cage `(vase !>(1)))
[p=%$ q=[#t/@ud q=1]]

(That last cage’s p means that the value is a constant.)

We would be remiss to not also address arch:

  • An arch is basically a file directory (in %clay) or a list of store paths (in %gall).

With the completion of this exercise, you have seen how to alter and query state using command-line and agent-based tools. Next, we will take a look at other means for manipulating agent state.

Key Points

  • A %gall app can be outfitted with a helper core to provide necessary operations.