Haskell, or rather the GHC 8.0 is not the same Haskell I started learning a few years ago. Having played around with Agda over the last summer, I became more interested in dependent types and type systems in general. Recently, I played around with HAskell’s newer features, such ask
TypeFamilies. This article is a summary of what I’ve discovered.
Having recently decided to do some “real world” programming in Haskell, I started looking at developing a web application with a client and a server side. After a few weeks, it became obvious that this was no small feat. Having settled on the rough shape of the architecture, namely a REST-full(/less) API on the server side and a client written in JS (probably GHCJS actually), I started exploring the ways in which the client and server will communicate.
Since JSON has become the de facto standard for data exchange between server and client on the Internet, I started exploring Haskell’s capabilities in this area. Typing “haskell json” into google immediately brings up aeson, the most widely know and used (probably) JSON library. The big advantage of aeson is the ease of the
FromJSON class instance deriving for record types in Haskell.
However, after playing around with Haskell’s record types along with aeson, there were a few things I found annoying, namely the record name clashes (which have been fixed by
-XDuplicateRecordFields in GHC 8.0.1, but still) and the sometimes cumbersome default encoding of some of the more complex Haskell data types. Whilst I could have read the documentation and tweaked the default settings, I decided to go down the “unnecessarily complicated” route and come up with some custom types to encode JSON objects inside Haskell.
JSON like types
Inspired by the Servant library for Type-level web APIs, I decided to mimic their type system to encode JSON data in Haskell. As a simple example, the authors of Servant give the following type of a service in Servant:
In the authors’ own words
The Haskell type
Echodescribes an API that accepts only GET requests directed at the route /echo. It expects a request body of content type text/plain, and produces a result of the same content type. In the Haskell world, the content will be treated as a value of type String.
The type above uses a feature of Haskell called Type-Level Literals, which allows one to use strings at the type level. Taking cue from the Servant framework, I created a
newtype, wrapping any existing type
t in a type
name :> t where
name is a type level string:
Using this type, one can annotate any type with a description. For example, one could write:
to denote the fact that “Sam” is in fact a name. The reason for having such an annotated type becomes obvious, when we want to create an instance of the
Thus, from the example above, calling aeson’s
encode function results in: