JSON-RPC is a simple and well supported protocol for
remote procedure calls over HTTP,
supporting both synchronous remote methods calls
and asynchronous notifications. We want to access JSON-RPC from Haskell,
but in a principled way. This blog post discusses the design and user-facing interface
a new library for JSON-RPC that makes use of the
remote monad design pattern
To give an example from the specification, consider calling
subtract, with the arguments
--> is the data sent from client to server, and
<-- is what the
server responds. The packet being sent is a simple JSON object, as is the
reply from the server.
The JSON-RPC protocol supports batching (sending several method calls and notifications at the same time) and is easy to debug, because it is straightforward to read JSON structures. Furthermore, by using JSON-RPC we can implement our clients in Haskell, and be agnostic about what language or framework the server is written in.
There are at least five existing Haskell libraries that support JSON-RPC. So why a new library? We wanted to build our own because we saw a way of simplifying the API considerably, while still allowing access to all the capabilities of JSON-RPC. Specifically, by using the remote monad design pattern we can automate taking advantage of the batch capability, amortizing the cost of the remote call. Rather than have separate entry points for batched and singleton calls, a single entry point can provide both batched and singleton calls. The library also acts as a case study of using the remote monad.
Basic Design of the JSON-RPC API
We center our design around the
This is a classical remote monad design - a restricted monad, a small number of primitives
for this monad, and a
Session is an abstract handle to the web server
we want to talk to; we’ll come back to how to generate a
This API gives an (aeson) Value-based access to JSON-RPC. Adding specific primitives gives
stronger typing. As an example, consider providing
say notifications, that make the remote
server say things, a
temperature method that returns the remote server’s temperature,
uptime method that returns the uptime of a specific service.
As an example we have our typed API for
say, saying “Hello, World!”,
getting the temperature, and the uptime of “orange”.
send can be used
multiple times if needed, acting as translation between our
IO monad, and the remote
Furthermore, the following JSON-RPC interaction with the server would be a valid
trace of interactions for the above example.
In this interaction:
- notifications are methods without id’s, and do not have replies, and
- methods use an id to tag a result.
This usage is reasonable, but we can do better. We want to bundle together
notifications and methods, to amortize the cost of network traffic, but
without comprising the API. Specifically, we want users to just write code
send, and the RPC library to figure out the best bundling possible.
The Remote Monad
In the remote monad theory, there are two key concepts:
- Splitting the monadic primitives into commands and procedures.
- Commands do not have a result value (typically
()in Haskell), and
- Procedures have a result.
There are restrictions on commands and procedures, specifically that they must be serializable.
- Commands do not have a result value (typically
- Choosing a bundling strategy. There are two strategies that were documented in the original paper
and one new bundling strategy that we are investigating.
- Weak - a bundle of a single command or a single procedure, or
- Strong - a bundle of a sequence of commands, optionally terminated by a procedure, or
- Applicative - a bundle of a sequence of commands and procedures, held together using an applicative functor.
By factoring our primitives into commands and procedures, we can automatically split up a monadic computation
into maximal bundles, and then use a transport layer to send, execute and get
the result from each bundle. The good news is there is a library, called
that has a plug-and-play API. If we provide the best bundling transport we can,
then the library can pick the best way of splitting up the monadic computation
into our bundles.
Considering JSON-RPC, the concept of notification and method map straight onto the remote monad concepts of commands and procedures. This makes things straightforward.
You can create a JSON-RPC instance using
which takes an encoding of how to send values to a
remote server, and returns a
SendAPI :~> IO is a natural transformation.
SendAPI :~> IO is a functor
and isomorphic to
∀ a. SendAPI a -> IO a.
this transformation is how you run
SendAPI is a deep embedding of
both synchronous and asynchronous communications of JSON
send with the
RPC monadic remote commands
factors up the specifics commands, and calls the
natural transformation argument with either
weakSession, every primitive causes its own
ASync; there is no complex bundling.
Putting this together, we get
Having selected the weak remote monad, we have the weakest JSON-RPC interaction - four transactions.
The strong remote monad bundles together commands, where possible, to amortize
the cost of the remote call. In our example above, we have two notifications,
and a method call. We want to combine them together. We do so by using the
Now, we get a two transactions, which conforms to the JSON-RPC specification.
The same RPC
send command gives better bundling, solely from changing the
strategy from a
weakSession to a
Now, the JSON-RPC specification says
- “The Server MAY process a batch rpc call as a set of concurrent tasks, processing them in any order and with any width of parallelism.”
This is where it gets interesting. The RPC monad reflects the concurrency
policy of the the server, up to method calls. If you wish to insist on
sequentiality, then use the
One obvious question is “can we improve this reflection of concurrency, and bundle method calls as well as notification?” Yes! For example, Haxl, a Haskell DSL for database access, uses an even stronger bundling strategy, which we call the applicative bundling strategy. In this strategy, methods that are expressed using applicative functors can be bundled together, as well as commands that can already be bundled.
remote-json library supports applicative bundling. Again, the details
are abstracted by the library, and again the monad and applicative functor
reflect the concurrency semantics of the server. We’ve added a new remote
uptime that returns the uptime of a specific machine, to aid
the demonstration of batching, and reworked the computation to use applicative.
The packet now includes two notifications and two methods. (Remember, a notification is a JSON-RPC method that has no id tag.)
When Applicative do
arrives with GHC 8, we can take immediate advantage of this, and start using
do-notation to write out remote applicative functors. In fact, we can
interperse our usage of monads and applicative
RPC, and the package
will choose the best bundling it can for the specific strategy.
We have written a remote monad for JSON-RPC. The library automatically bundles
send to attempt to amortize the cost of the remote procedure call. It
simplifies making multiple requests, without resorting to specialized variants
These idea has been used before. For example,
Haxl lead the charge of using the
applicative bundling strategy,
and Oleg implemented a remote monad in OCaml several years ago.
And we’ve been using predecessors to
remote-json for several years now.
Feel free to try
remote-json, and if you are adventurous,
Or roll your own remote monad. Once you know the pattern, it’s quite straightforward!
In a future blog post, we will look at our JSON-RPC server.
Justin Dawson and Andy Gill
Resources and Related Work
- There is no fork: an abstraction for efficient, concurrent, and concise data access, Simon Marlow and Louis Brandy and Jonathan Coens and Jon Purdy.
- Semi-implicit batched remote code execution as staging, Oleg Kiselyov
- The remote monad design pattern
Existing JSON-RPC packages
json-rpcby Jean-Pierre Rupp
json-rpc-clientby Kristen Kozak
jsonrpc-conduitby Gabriele Sales
colchisby Daniel Díaz Carrete
jmacro-rpcby Gershom Bazerman
Our Publications about the Remote Monad
A. Gill, N. Sculthorpe, J. Dawson, A. Eskilson, A. Farmer, M. Grebe, J. Rosenbluth, R. Scott, and J. Stanton, “The remote monad design pattern,” in Proceedings of the 8th ACM SIGPLAN Symposium on Haskell. New York, NY, USA: ACM, 2015, pp. 59–70.
M. Grebe and A. Gill, “Haskino: A remote monad for programming the Arduino,” in Practical Aspects of Declarative Languages, ser. Lecture Notes in Computer Science, 2016.