2015-11-18 02:38:47 +03:00
|
|
|
---
|
|
|
|
next: false
|
2015-12-02 02:00:02 +03:00
|
|
|
sort: 2
|
2015-11-18 02:38:47 +03:00
|
|
|
title: Network Messages
|
|
|
|
---
|
|
|
|
|
|
|
|
Enough of pure hoonery. Let's get to the good stuff. Let's get
|
|
|
|
our planets to talk to each other.
|
|
|
|
|
|
|
|
Of course, for talking to be of any use, we need someone
|
|
|
|
listening. What we've written up until now are just shell
|
|
|
|
commands that produce a value and then disappear. We need an
|
|
|
|
actual app to listen for messages from another planet. Let's
|
|
|
|
take a look at a very basic one.
|
|
|
|
|
|
|
|
```
|
|
|
|
:: There is no love that is not an echo
|
|
|
|
::
|
|
|
|
:::: /hoon/echo/ape
|
|
|
|
::
|
|
|
|
/? 314
|
|
|
|
!:
|
|
|
|
|_ [bowl state=~]
|
|
|
|
++ poke-noun
|
|
|
|
|= arg=*
|
2015-12-02 02:00:02 +03:00
|
|
|
^- [(list) _+>.$]
|
2015-11-18 02:38:47 +03:00
|
|
|
~& [%argument arg]
|
|
|
|
[~ +>.$]
|
|
|
|
--
|
|
|
|
```
|
|
|
|
|
|
|
|
This is a very simple app that does only one thing. If you poke
|
|
|
|
it with a value it prints that out. You have to start the app,
|
2015-12-02 02:00:02 +03:00
|
|
|
then you can poke it from the command line with the following
|
|
|
|
commands:
|
2015-11-18 02:38:47 +03:00
|
|
|
|
|
|
|
```
|
2015-12-02 02:00:02 +03:00
|
|
|
~fintud-macrep:dojo> |start %echo
|
2015-11-18 02:38:47 +03:00
|
|
|
>=
|
2015-12-02 02:00:02 +03:00
|
|
|
~fintud-macrep:dojo> :echo 5
|
2015-11-18 02:38:47 +03:00
|
|
|
[%argument 5]
|
2015-12-02 02:00:02 +03:00
|
|
|
>=
|
|
|
|
~fintud-macrep:dojo> :echo [1 2]
|
2015-11-18 02:38:47 +03:00
|
|
|
[%argument [1 2]]
|
2015-12-02 02:00:02 +03:00
|
|
|
>=
|
2015-11-18 02:38:47 +03:00
|
|
|
```
|
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
> There is currently a bug where the `%argument` lines are
|
|
|
|
> printed *above* the line you entered, so your output may not
|
|
|
|
> look exactly like this.
|
|
|
|
|
2015-11-18 02:38:47 +03:00
|
|
|
Most of the app code should be simple enough to guess its
|
|
|
|
function. The important part of this code is the definition of
|
|
|
|
`++poke-noun`.
|
|
|
|
|
|
|
|
Once an app starts, it's always on in the background, and you
|
|
|
|
interact with it by sending it messages. The most
|
|
|
|
straightforward way to do that is to poke it from the command
|
|
|
|
line. When you do that, `++poke-noun` is called from your app.
|
|
|
|
|
|
|
|
In our case, `++poke-noun` takes an argument `arg` and prints it
|
|
|
|
out with `~&`. This is an unusual rune that formally "does
|
|
|
|
nothing", but the interpreter detects it and printfs the first
|
|
|
|
child. This is a slightly hacky way of printing to the console,
|
|
|
|
and we'll get to the correct way later on.
|
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
But what does `++poke-noun` produce? Recall that `^-` casts to a
|
|
|
|
type. In this case, it's declaring that end result of the
|
|
|
|
function will be of type `[(list) _+>.$]`. But what does this
|
|
|
|
mean?
|
|
|
|
|
|
|
|
The phrase to remember is "a list of moves and our state". Urbit
|
|
|
|
is a message passing system, so whenver we want to do something
|
|
|
|
that interacts with the rest of the system we send a message.
|
|
|
|
Thus, a move is arvo's equivalent of a syscall. The first
|
|
|
|
thing that `++poke-noun` produces is a list of messages, called
|
2015-11-18 02:38:47 +03:00
|
|
|
"moves". In this case, we don't actually want the system to do
|
2015-12-02 02:00:02 +03:00
|
|
|
anything, so we produce the empty list, `~` (in the `[~ +>.$]`
|
|
|
|
line).
|
2015-11-18 02:38:47 +03:00
|
|
|
|
|
|
|
The second thing `++poke-noun` produces is our state. `+>.$`
|
|
|
|
refers to a particular address in our subject where our formal
|
|
|
|
app state is stored. It'll become clear why this is later on,
|
|
|
|
but for now pretend that `+>.$` is a magic invocation that means
|
|
|
|
"app state".
|
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
Let's look at another example. Say we want to only accept a
|
|
|
|
number, and then print out the square of that number.
|
2015-11-18 02:38:47 +03:00
|
|
|
|
|
|
|
```
|
|
|
|
/? 314
|
|
|
|
!:
|
|
|
|
|_ [bowl state=~]
|
|
|
|
::
|
|
|
|
++ poke-atom
|
|
|
|
|= arg=@
|
2015-12-02 02:00:02 +03:00
|
|
|
^- [(list) _+>.$]
|
2015-11-18 02:38:47 +03:00
|
|
|
~& [%square (mul arg arg)]
|
|
|
|
[~ +>.$]
|
|
|
|
--
|
|
|
|
```
|
|
|
|
|
|
|
|
A few things have changed. Firstly, we no longer accept
|
|
|
|
arbitrary nouns because we can only square atoms. Thus, our
|
|
|
|
argument is now `arg=@`. Secondly, it's `++poke-atom` rather
|
|
|
|
than `++poke-noun`.
|
|
|
|
|
|
|
|
Are there other `++poke`s? Definitely. In fact, `noun` and
|
|
|
|
`atom` are just two of arbitrarily many "marks". A mark is
|
2015-12-02 02:00:02 +03:00
|
|
|
fundamentally a type definition, but accessible at the arvo
|
|
|
|
level. Each mark is defined in the `/mar` directory. Some marks
|
|
|
|
have conversion routines to other marks, and some have diff,
|
|
|
|
patch, and merge algorithms. None of these are required for a
|
|
|
|
mark to exist, though.
|
2015-11-18 02:38:47 +03:00
|
|
|
|
|
|
|
`noun` and `atom` are two of dozens of predefined marks, and the
|
|
|
|
user may add more at will. The type associated with `noun` is
|
|
|
|
`*`, and the type associated with `atom` is `@`.
|
|
|
|
|
|
|
|
Data constructed on the command line is by default marked with
|
|
|
|
`noun`. In this case, the app is expecting an atom, so we have
|
2015-12-02 02:00:02 +03:00
|
|
|
to explicitly mark the data with `atom`. Try the following
|
|
|
|
commands:
|
2015-11-18 02:38:47 +03:00
|
|
|
|
|
|
|
```
|
2015-12-02 02:00:02 +03:00
|
|
|
~fintud-macrep:dojo> |start %square
|
|
|
|
>=
|
|
|
|
~fintud-macrep:dojo> :square 6
|
2015-11-18 02:38:47 +03:00
|
|
|
gall: %square: no poke arm for noun
|
2015-12-02 02:00:02 +03:00
|
|
|
~fintud-macrep:dojo> :square &atom 6
|
2015-11-18 02:38:47 +03:00
|
|
|
[%square 36]
|
2015-12-02 02:00:02 +03:00
|
|
|
>=
|
2015-11-18 02:38:47 +03:00
|
|
|
```
|
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
> Recall the bug where `%square` may get printed above the input
|
|
|
|
> line.
|
|
|
|
|
2015-11-18 02:38:47 +03:00
|
|
|
Marks are powerful, and they're the backbone of urbit's data
|
|
|
|
pipeline, so we'll be getting quite used to them.
|
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
**Exercises**:
|
2015-11-18 02:38:47 +03:00
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
- Write an app that computes fizzbuzz on its input (as in the
|
|
|
|
previous section).
|
2015-11-18 02:38:47 +03:00
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
- One way of representing strings is with double quoted strings
|
|
|
|
called "tapes". The hoon type is `tape`, and there is a
|
|
|
|
corresponding mark with the same name. Write an app that
|
|
|
|
accepts a tape and prints out `(flop argument)`, where
|
|
|
|
`argument` is the input. What does this do?
|
|
|
|
|
|
|
|
|
|
|
|
Let's write our first network message! Here's `/ape/pong.hoon`:
|
|
|
|
|
|
|
|
```
|
|
|
|
/? 314
|
|
|
|
|%
|
|
|
|
++ move ,[bone term path *]
|
|
|
|
--
|
|
|
|
!:
|
|
|
|
|_ [bowl state=~]
|
|
|
|
::
|
|
|
|
++ poke-urbit
|
|
|
|
|= to=@p
|
|
|
|
^- [(list move) _+>.$]
|
|
|
|
[[[ost %poke /sending [to %pong] %atom 'howdy'] ~] +>.$]
|
|
|
|
::
|
|
|
|
++ poke-atom
|
|
|
|
|= arg=@
|
|
|
|
^- [(list move) _+>.$]
|
|
|
|
~& [%receiving (,@t arg)]
|
|
|
|
[~ +>.$]
|
|
|
|
::
|
|
|
|
++ coup |=(* [~ +>.$])
|
|
|
|
--
|
|
|
|
```
|
|
|
|
|
|
|
|
Run it with these commands:
|
|
|
|
|
|
|
|
```
|
|
|
|
~fintud-macrep:dojo> |start %pong
|
|
|
|
>=
|
|
|
|
~fintud-macrep:dojo> :pong &urbit ~sampel-sipnym
|
|
|
|
>=
|
|
|
|
```
|
|
|
|
|
|
|
|
Replace `~sampel-sipnym` with another urbit. Don't forget to
|
|
|
|
start the `%pong` app on that urbit too. You should see, on the
|
|
|
|
foreign urbit, this output:
|
|
|
|
|
|
|
|
```
|
|
|
|
[%receiving 'howdy']
|
|
|
|
```
|
|
|
|
|
|
|
|
Most of the code should be straightforward. In `++poke-atom`,
|
|
|
|
the only new thing is the expression `(,@t arg)`. As we already
|
|
|
|
know, `@t` is the type of "cord" text strings. `,` is an
|
|
|
|
operator that turns a type into a validator function -- that is,
|
|
|
|
a function whose domain is all nouns and range is the given type,
|
|
|
|
and which is identity when the domain is restricted to the given
|
|
|
|
type. In simpler terms, it's a function that coerces any value
|
|
|
|
to the given type. We call this `,@t` function on the argument.
|
|
|
|
This coerces the argument to text, so that we can print it out
|
|
|
|
prettily.
|
|
|
|
|
|
|
|
The more interesting part is in `++poke-urbit`. The `urbit` mark
|
|
|
|
is an urbit identity, and the hoon type associated with it is
|
|
|
|
`@p` (the "p" stands for "phonetic base").
|
|
|
|
|
|
|
|
Recall that in a `++poke` arm we produce "a list of moves and our
|
|
|
|
state". Until now, we've left the list of moves empty, since we
|
|
|
|
haven't wanted to tell arvo to do anything in particular. Now we
|
|
|
|
want to send a message to another urbit. Thus, we produce a list
|
|
|
|
with one element:
|
|
|
|
|
|
|
|
```
|
|
|
|
[ost %poke /sending [to %pong] %atom 'howdy']
|
|
|
|
```
|
|
|
|
|
|
|
|
The general form of a move is
|
|
|
|
|
|
|
|
`[bone term path *]`
|
|
|
|
|
|
|
|
If you look up `++bone` in `hoon.hoon`, you'll see that it's a
|
|
|
|
number (`@ud`), and that it's an opaque reference to a duct.
|
|
|
|
`++duct` in hoon.hoon is a list of `wire`s, where `wire` is an
|
|
|
|
alias for `path`. `++path` is a list of `span`s, which are ASCII
|
|
|
|
text. Thus, a duct is a list of paths, and a bone is an opaque
|
|
|
|
reference to that duct (in the same way that a Unix file
|
|
|
|
descriptor is an opaque reference to a file structure). Thus,
|
|
|
|
the center of all this is the concept of a "duct".
|
|
|
|
|
|
|
|
A duct is stack of causes, represented as paths. At the bottom
|
|
|
|
of every duct is a unix event, such as a keystroke, network
|
|
|
|
packet, file change, or timer event. When arvo is given this
|
|
|
|
event, it routes the event to appropriate kernel module for
|
|
|
|
handling.
|
|
|
|
|
|
|
|
Sometimes, the module can immediately handle the event and
|
|
|
|
produce any necessary results. Otherwise, it asks other kernel
|
|
|
|
modules or applications to do certain things, and produces the
|
|
|
|
result from that. When it sends a message to another kernel
|
|
|
|
module or application, it sends it "along" the duct it was given,
|
|
|
|
plus with a new path. Arvo pushes the new path onto the duct.
|
|
|
|
Now the duct has two entries, with the unix even on the bottom
|
|
|
|
and the kernel module that handled it next. This process can
|
|
|
|
continue indefinitely, adding more and more layers onto the duct.
|
|
|
|
When an entity produces a result, a layer is popped off the duct.
|
|
|
|
|
|
|
|
In effect, a duct is an arvo-level call stack. The duct system
|
|
|
|
creates a structured message-passing system. It's worth noting
|
|
|
|
that while in traditional call stacks a function call happens
|
|
|
|
synchronously and returns exactly once, in arvo multiple moves
|
|
|
|
can be sent at once, they are evaluated asynchronously, and each
|
|
|
|
one may be responded to zero or more times.
|
|
|
|
|
|
|
|
Anyhow, the point is that whatever caused `++poke-urbit` to be
|
|
|
|
called is also the root cause for the network message we're
|
|
|
|
trying to send. Thus, we say to send the network message along
|
|
|
|
the given bone `ost`. Of course, we have to push a layer to the
|
|
|
|
duct. This layer can have any data we want in it, but we don't
|
|
|
|
need anything specific here, so we just use `/sending`. If we
|
|
|
|
were expecting a response (which we're not), it would come back
|
|
|
|
along the `/sending` path. It's a good idea for debugging
|
|
|
|
purposes to make the path human-readable, but it's not necessary.
|
|
|
|
|
|
|
|
Looking back at the general form of a move, there is a `term`,
|
|
|
|
which in this case is `%poke`. This is the name of the
|
|
|
|
particular kind of move we're sending. If you think of a move as
|
|
|
|
a syscall (which you should), then this `term` is the name of the
|
|
|
|
syscall. Common ones include: `%poke`, to message an app;
|
|
|
|
`%warp`, to read from the filesystem; `%wait`, to set a timer;
|
|
|
|
and `%them`, to send an http request.
|
|
|
|
|
|
|
|
The general form ends with `*` since each type of move takes
|
|
|
|
different data. In our case, a `%poke` move takes a target
|
|
|
|
(urbit and app) and marked data and pokes that app on that urbit
|
|
|
|
with that data. `[to %pong]` is the target urbit and app,
|
|
|
|
`%atom` is the mark`, and `'howdy'` is the data.
|
|
|
|
|
|
|
|
When arvo receives a `%poke` move, it calls the appropriate
|
|
|
|
`++poke`. The same mechanism is used for sending messages
|
|
|
|
between apps on the same urbit as for sending messages between
|
|
|
|
apps on different urbits.
|
|
|
|
|
|
|
|
> We said earlier that we're not expecting a response. This is
|
|
|
|
> not entirely true: the `++coup` is called when we receive
|
|
|
|
> acknowledgment that the `++poke` was called. We don't do
|
|
|
|
> anything with this information right now, but we could.
|
|
|
|
|
|
|
|
**Exercises**:
|
|
|
|
|
|
|
|
- Extend either of the apps in the first two exercises to accept
|
|
|
|
input over the network in the same way as `pong`.
|
|
|
|
|
|
|
|
- Modify `pong` to print out a message when it receives an ack.
|
|
|
|
|
|
|
|
- Write two apps, `even` and `odd`. When you pass an atom to
|
|
|
|
`even`, check whether it's even. If so, divide it by two and
|
|
|
|
recurse; otherwise, poke `odd` with it. When `odd` recieves
|
|
|
|
an atom, check whether it's equal to one. If so, terminate,
|
|
|
|
printing "%success". Otherwise, check whether it's odd. If
|
|
|
|
so, multiply it by three, add one, and recurse; otherwise, poke
|
|
|
|
`even` with it. multiply it by three and add one. When either
|
|
|
|
app receives a number, print it out along with the name of the
|
|
|
|
app. In the end, you should be able to watch Collatz's
|
|
|
|
conjecture play out between the two apps. Sample output:
|
|
|
|
|
|
|
|
```
|
|
|
|
~fintud-macrep:dojo> :even &atom 18
|
|
|
|
[%even 18]
|
|
|
|
[%odd 9]
|
|
|
|
[%even 28]
|
|
|
|
[%even 14]
|
|
|
|
[%odd 7]
|
|
|
|
[%even 22]
|
|
|
|
[%odd 11]
|
|
|
|
[%even 34]
|
|
|
|
[%odd 17]
|
|
|
|
[%even 52]
|
|
|
|
[%even 26]
|
|
|
|
[%odd 13]
|
|
|
|
[%even 40]
|
|
|
|
[%even 20]
|
|
|
|
[%even 10]
|
|
|
|
[%odd 5]
|
|
|
|
[%even 16]
|
|
|
|
[%even 8]
|
|
|
|
[%even 4]
|
|
|
|
[%even 2]
|
|
|
|
%success
|
|
|
|
```
|
2015-11-18 02:38:47 +03:00
|
|
|
|
2015-12-02 02:00:02 +03:00
|
|
|
- Put `even` and `odd` on two separate ships and pass the
|
|
|
|
messages over the network.
|