Your first node¶
A Pony program that a running BEAM can call, and that answers. The whole program is on this page, and it runs.
The shape of the exchange¶
Elixir sends {Pid, binary}; the node answers {reply, binary, Pid}. The Pid
is the caller's, so the answer goes back to whoever asked.
The Elixir side¶
Save this as demo.exs:
{:ok, hostname} = :inet.gethostname
# The node is named after the machine's host name without its domain.
host = hostname |> to_string() |> String.downcase() |> String.split(".") |> hd()
pony = {:any, :"pony@#{host}"}
# The Pony program connects after it starts, so give it a moment.
Process.sleep(2_000)
for n <- 1..5 do
send(pony, {self(), "#{n}: Hi!"})
receive do
{:reply, text, _from} -> IO.puts("Elixir: #{text}")
after
3_000 -> IO.puts("Elixir: nothing came back")
end
end
Start it and leave it running:
{:any, :"pony@hostname"} addresses the node rather than a process on it, and
the cookie has to be the one the node was created with. Nothing is listening
yet; the next step is the node.
The Pony side¶
use "erl_interface_pony"
actor Main
let _env: Env
let _node: EInterface
new create(env: Env) =>
_env = env
_node = EInterface("pony", "secretcookie")
match _node.connect("demo@localhost")
| ConnectionFailed =>
_env.out.print("no node there (" + _node.last_error() + ")")
return
| ConnectionSucceeded =>
_env.out.print("connected as " + _node.this_nodename())
end
listen()
fun ref listen() =>
match _node.receive_with_timeout(5_000)
| let message: EMessage =>
answer(message)
listen()
| ReceiveTimedOut =>
_env.out.print("nothing more to do")
| ReceiveFailed =>
_env.out.print("the other side stopped")
end
fun ref answer(message: EMessage ref) =>
match ErlangShapes.pair(ErlangTermDecoder.decode(message))
| (let sender: ErlangPid, let text: ErlangBinary) =>
_env.out.print("asking: " + text.value)
let reply: Array[ErlangTerm] val = [
ErlangAtom("reply")
ErlangBinary("pong: " + text.value)
sender
]
try
let encoded = EMessage.begin()
encoded.encode(ErlangTuple(reply))?
_node.send_to(sender, encoded, 500)
end
else
_env.out.print("not a {Pid, Binary}")
end
Build and run it in a second terminal:
The first terminal starts printing:
What happened there¶
connect joins the BEAM as a node called pony on this host. The cookie is the
distribution cookie, the same one Elixir was started with.
receive_with_timeout(5_000) waits up to five seconds for a message and answers
with an EMessage; 0 waits forever.
ErlangShapes.pair(ErlangTermDecoder.decode(message)) is where bytes become
Pony values: either two elements, a pid and a binary, or two Nones for
anything else. That is the only place the message's shape is checked.
The reply is a Pony array of terms encoded into a message, then sent to the pid that asked.
Where to go next¶
- Reading messages — shapes, values and keys, without casts.
demo/in the repository is this program with a reply counter.corral_demo/is the same library consumed through corral, with no Erlang node needed.