6. Pokes
In this lesson we'll look at sending and receiving one-off messages called %poke
s. We'll look at the +on-poke
agent arm which handles incoming pokes. We'll also introduce the +on-agent
arm, and look at the one kind of response it can take - a %poke-ack
.
Receiving a poke
Whenever something tries to poke your agent, Gall calls your agent's +on-poke
arm and give it the $cage
from the poke as its sample. The +on-poke
arm will produce a (quip card _this)
. Here's how it would typically begin:
++ on-poke
|= [=mark =vase]
^- (quip card _this)
...
The sample of the gate is usually specified as a cell of $mark
and $vase
rather than just $cage
, simply because it's easier to work with.
Typically, you'd first test the $mark
with something like a wutlus ?+
expression, passing unexpected $mark
s to default-agent, which just crashes. We'll look at custom $mark
s in a subsequent lesson, but the basic pattern looks like:
?+ mark (on-poke:def mark vase)
%noun ...
%something-else ...
...
==
After testing the $mark
, you'd usually extract the $vase
to the expected type, and then apply whatever logic you need. For example:
=/ action !<(some-type vase)
?- -.action
%foo ...
%bar ...
...
==
Your agent will then produce a list of $card
s to be sent off and a new, modified state, as appropriate. We'll go into subscriptions in the next lesson, but just to give you an idea of a typical pattern: An agent for a chat app might take new messages as pokes, add them to the list of messages in its state, and send out the new messages to subscribed chat participants as $gift
s.
As discussed in the previous lesson, Gall will automatically send a %poke-ack
$gift
back to wherever the poke came from. The %poke-ack
will be a nack if your agent crashed while processing the poke, and an ack otherwise. If it's a nack, the $tang
in the %poke-ack
will contain a stack trace of the crash.
As a result, you do not need to explicitly send a %poke-ack
. Instead, you would design your agent to handle only what you expect and crash in all other cases. You can crash by passing the $cage
to default-agent, or just with a !!
. In the latter case, if you want to add an error message to the stack trace, you can do so like:
~| "some error message"
!!
This will produce a trace that looks something like:
/sys/vane/gall/hoon:<[1.372 9].[1.372 37]>
/app/pokeme/hoon:<[31 3].[43 5]>
/app/pokeme/hoon:<[32 3].[43 5]>
/app/pokeme/hoon:<[34 5].[42 7]>
/app/pokeme/hoon:<[35 5].[42 7]>
/app/pokeme/hoon:<[38 7].[41 27]>
/app/pokeme/hoon:<[39 9].[40 11]>
"some error message"
/app/pokeme/hoon:<[40 9].[40 11]>
Note that the $tang
in the nack is just for debugging purposes, you should not try to pass actual data by encoding it in the nack $tang
.
Sending a poke
An agent can send pokes to other agents by producing %poke
$card
s. Any agent arm apart from +on-peek
and +on-save
can produce such $card
s. The arms would typically produce the (quip card _this)
like so:
:_ this
:~ [%pass /some/wire %agent [~target-ship %target-agent] %poke %some-mark !>('some data')]
==
The colcab (:_
) rune makes an inverted cell, it's just :-
but with the head and tail swapped. We use colcab to produce the (quip card _this)
because the list of cards is "heavier" here than the new agent core expression (.this
), so it makes it more readable.
Receiving the %poke-ack
%poke-ack
The pokes will be processed by their targets as described in the previous section, and they'll %give
back a %poke-ack
on the $wire
you specified (/some/wire
in the previous example). When Gall gets the %poke-ack
back, it will call the +on-agent
arm of your agent, with the $wire
it came in on and the %poke-ack
itself in a sign:agent:gall
. Your +on-agent
arm would therefore begin like so:
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
...
A $sign:agent:gall
(henceforth just $sign
) is defined in /sys/lull.hoon
as:
+$ sign
$% [%poke-ack p=(unit tang)]
[%watch-ack p=(unit tang)]
[%fact =cage]
[%kick ~]
==
It's basically the same as a $gift
, but incoming instead of outgoing.
The simplest way to handle a %poke-ack
by passing it to default-agent's +on-agent
arm, which will just print an error message to the terminal if it's a nack, and otherwise do nothing. Sometimes you'll want your agent to do something different depending on whether the poke failed or succeeded (and therefore whether it's a nack or an ack).
You should always route on wire before sign, never sign before wire. You might do something like:
?+ wire (on-agent:def wire sign)
[%some %wire ~] ...
...
==
After that, you'll need to see what kind of $sign
it is:
?+ -.sign (on-agent:def wire sign)
%poke-ack ...
...
Then, you can tell whether it's an ack or a nack by testing whether the (unit tang)
in the %poke-ack
is null:
?~ p.sign
...(what to do if the poke succeeded)...
...(what to do if the poke failed)...
Finally, you can produce the (quip card _this)
.
Example
We're going to look at a couple of agents to demonstrate both sending and receiving pokes. Here's the first, an agent that receives pokes:
This is a very simple agent that just has .val
, a number, in its state. It will take pokes that either increment or decrement .val
. Here's its +on-poke
arm:
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?+ mark (on-poke:def mark vase)
%noun
=/ action !<(?(%inc %dec) vase)
?- action
%inc `this(val +(val))
%dec
?: =(0 val)
~| "Can't decrement - already zero!"
!!
`this(val (dec val))
==
==
It only expects pokes with a %noun
mark, and passes all others to +on-poke:def
, which just crashes. For %noun
pokes, it expects to receive either %inc
or %dec
in the $vase
. If it's %inc
, it produces a new .this
with .val
incremented. If it's %dec
, it produces .this
with .val
decremented, or crashes if .val
is already zero.
Let's try it out. Save the agent above as /app/pokeme.hoon
in the %base
desk and |commit %base
. Then, start it up with |rein %base [& %pokeme]
. We can check its initial state with +dbug
:
> 0
> :pokeme +dbug [%state %val]
>=
Next, we'll try poking it. The Dojo lets you poke agents with the following syntax:
:agent-name &some-mark ['some' 'noun']
If the $mark
part is omitted, it'll just default to %noun
. Since our agent only takes a %noun
mark, we can skip that. The rest will be packed in a $vase
by the Dojo and delivered as a poke, so we can do:
> :pokeme %inc
>=
If we now look at the state with +dbug
, we'll see the poke was successful and it's been incremented:
> 1
> :pokeme +dbug [%state %val]
>=
Let's try decrement:
> :pokeme %dec
>=
> 0
> :pokeme +dbug [%state %val]
>=
As you can see, it's back at zero. If we try again, we'll see it fails, and the Dojo will print the $tang
in the %poke-ack
nack:
> :pokeme %dec
/sys/vane/gall/hoon:<[1.828 9].[1.828 37]>
/app/pokeme/hoon:<[35 3].[48 5]>
/app/pokeme/hoon:<[36 3].[48 5]>
/app/pokeme/hoon:<[38 5].[47 7]>
/app/pokeme/hoon:<[39 5].[47 7]>
/app/pokeme/hoon:<[43 7].[46 27]>
/app/pokeme/hoon:<[44 9].[45 11]>
"Can't decrement - already zero!"
/app/pokeme/hoon:<[45 9].[45 11]>
dojo: app poke failed
Here's a second agent. It takes a poke of %inc
or %dec
like before, but rather than updating its own state, it sends two pokes to %pokeme
, so %pokeme
's state will be incremented or decremented by two.
Here's the +on-poke
arm:
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?+ mark (on-poke:def mark vase)
%noun
=/ action !<(?(%inc %dec) vase)
?- action
%inc
:_ this
:~ [%pass /inc %agent [our.bowl %pokeme] %poke %noun !>(%inc)]
[%pass /inc %agent [our.bowl %pokeme] %poke %noun !>(%inc)]
==
%dec
:_ this
:~ [%pass /dec %agent [our.bowl %pokeme] %poke %noun !>(%dec)]
[%pass /dec %agent [our.bowl %pokeme] %poke %noun !>(%dec)]
==
==
==
It's similar to %pokeme
, except it sends two %poke
$card
s to %pokeme
for each case, rather than modifying its own state. The %inc
pokes specify a $wire
of /inc
, and the %dec
pokes specify a $wire
of /dec
, so we can differentiate the responses. It also has the following +on-agent
:
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
?+ wire (on-agent wire sign)
[%inc ~]
?. ?=(%poke-ack -.sign)
(on-agent wire sign)
?~ p.sign
%- (slog '%pokeit: Increment poke succeeded!' ~)
`this
%- (slog '%pokeit: Increment poke failed!' ~)
`this
::
[%dec ~]
?. ?=(%poke-ack -.sign)
(on-agent wire sign)
?~ p.sign
%- (slog '%pokeit: Decrement poke succeeded!' ~)
`this
%- (slog '%pokeit: Decrement poke failed!' ~)
`this
==
+on-agent
tests the $wire
, checks if it's a %poke-ack
, and then prints to the terminal whether it succeeded or failed.
Save this agent to /app/pokeit.hoon
on the %base
desk, |commit %base
, and start it with |rein %base [& %pokeme] [& %pokeit]
.
Let's try it out:
%pokeit: Increment poke succeeded!
%pokeit: Increment poke succeeded!
> :pokeit %inc
>=
%pokeit
has received positive %poke-ack
s, which means both pokes succeeded. It could tell they were increments because the %poke-ack
s came back on the /inc
wire we specified. We can check the state of %pokeme
to confirm:
> 2
> :pokeme +dbug [%state %val]
>=
Let's try decrementing %pokeme
so val is 1, and then try a %dec
via %pokeit
:
> :pokeme %dec
>=
%pokeit: Decrement poke succeeded!
%pokeit: Decrement poke failed!
> :pokeit %dec
>=
The +on-agent
arm of %pokeit
has received one ack and one nack. The first took .val
to zero, and the second crashed trying to decrement below zero.
Summary
Incoming pokes go to the
+on-poke
arm of an agent.The
+on-poke
arm takes a$cage
and produces an(quip card _this)
.Gall will automatically return a
%poke-ack
to the poke's source, with a stack trace in the(unit tang)
if your agent crashed while processing the poke.Outgoing pokes can be sent by including
%poke
%pass
$card
s in the+quip
produced by most agent arms.%poke-ack
s in response to pokes you've sent will come in to the+on-agent
arm in a$sign
, on the$wire
you specified in the original%poke
$card
.You can poke agents from the Dojo with a syntax of
:agent &mark ['some' 'noun']
.
Exercises
Run through the example yourself on a fake ship if you've not done so already.
Have a look at the
+on-agent
arm of/lib/default-agent.hoon
to see how default-agent handles incoming$sign
s.Try modifying the
%pokeme
agent with another action of your choice (in addition to%inc
and%dec
).Try modifying the
%pokeit
agent to send your new type of poke to%pokeme
, and handle the%poke-ack
it gets back.
Last updated