Networking with hi¶
Hobbes supports native, typesafe client-server network programming in a very similar manner to its support for structured logfiles. Connection information alongside the types of data involved are exposed through an unqualifier much like LoadFile, in a manner mostly invisible to the user.
You can use Hobbes networking to perform actions on another host, such as gathering usage statistics, or performing administrative actions such as changing the log level.
Setting up the receiver¶
hi can be set to receive messages over the network. If we invoke hi with the -p
flag we can specify the port to listen on:
$ hi -p 8080
[...]
running a repl server at myhost:8080
>
We’ll call this the server, and next we’ll connect to it over the network.
Opening a connection¶
From another instance of hi, create a connection to the server using the Connect
unqualifier:
$ hi -s
> c = connection :: (Connect "myhost:8080" p) => p
Note
Unqualifiers
For more information about how this works, have a look at the LoadFile unqualifier, which we use to load data using the Hobbes persistence API.
We’ll call this the client. From here, you can inspect the details of the connection with printConnection
:
> printConnection(c)
id expr input output
-- ---- ----- ------
>
There’s not much here yet, so let’s create some functionality on the server.
Remote methods¶
Back on the server, create a new function called addOne:
> addOne = \x.x+1
>
Then on the client, let’s make that functionality available remotely. We’ll use the hobbes type negotiation mechanism to make sure all the types line up:
> receive(invoke(c, `addOne`, 12))
13
Wow! There’s some new Hobbes here, so let’s go through this line piece by piece.
The invoke
and receive
functions allow Hobbes to execute commands remotely and interpret the results. We call them together becuase the type information about the return value of the addOne
function is passed between them.
Secondly, the strange ‘quoted’ form of addOne
. The quoted form of the invocation isn’t a string - it’s parsed but as-yet unexecuted Hobbes in a form which can be serialised and set to a remote Hobbes process for invocation. It’s this mechanism that Hobbes uses to determine the return type of the method - on the remote process!
In this manner we’re able to execute functions on the server from the client - without any of the complex type negotionation or serialisation that we’d otherwise have to do.
Once the work has been executed remotely, the result has been serialised, sent across the network, deserialised and displayed in our local client prompt.
Inspection of the connection¶
If we use printConnection
to take another look at c
, we’ll see that the initial remote invocation of the function addOne
has had some effects:
> printConnection(c)
id expr input output
-- ---- ----- ------
1 int int addOne
Firstly, we can see that Hobbes has given the remote addOne
function a numeric ID - this means that future invocations will be much faster.
Secondly, Hobbes has used the connection to communicate with the remote host and find out the type of addOne
- a function that takes an int
and returns an int
.
Delayed Invocation¶
In the above example the type information Hobbes gathered from the server was made available at the first invocation of the method, using receive
. However, Hobbes has the ability to query type information from the server using the unqualifier mechanism much earlier, before the method is even invoked.
Go back to the server and add another method, addTwo:
addTwo = \x.x+2
Then on the client,
> remoteAddTwo = \x.receive(invoke(c, `addTwo`, x::int))
> printConnection(c)
id expr input output
-- ---- ----- ------
1 int int addOne
2 int int addTwo
In this example, remoteAddTwo
is a function defined by a lambda - that we haven’t yet called! All we’ve passed is the information about the input type - the int
argument to addTwo
- and the Hobbes server process has done all the type inference and returned the structured type data for us.
We can invoke the remote function in the usual way, by passing parameters to the function name:
> remoteAddTwo(3)
5
Errors¶
Because all the type information is evaluated on the remote host, any processing errors or type mismatches will also come from the other server. For example, try to invoke a function that doesn’t yet exist:
> receive(invoke(c, `addSeven`, 3))
stdin:1,1-33: Error from server: stdin:0,0-0: Undefined variable: 'addSeven'