XUtils

haskell-to-elm

Generate Elm types, encoders, and decoders from Haskell types


haskell-to-elm Hackage

haskell-to-elm is a library that takes Haskell type definitions as input and generates matching Elm type definitions and JSON encoders and decoders that match Aeson’s format.

The problem

Let’s say we’re building a web page with a Haskell backend and an Elm frontend.

We might have a Haskell type like this, that we pass to the frontend encoded as JSON. The JSON encoder is derived using the Aeson library.

data User = User
  { name :: Text
  , age :: Int
  } deriving (Generic, ToJSON)

We mirror the type on the Elm side and add a JSON decoder as follows:

type alias User =
    { name : String
    , age : Int
    }

decoder : Decoder User
decoder =
    Decode.map2 User
        (Decode.field "name" Decode.string)
        (Decode.field "age" Decode.int)

Now, let’s say we want to change a field in the backend:

-- Haskell
data User = User
  { name :: Text
--, age :: Int
  , birthday :: Date -- <---- new!
  } deriving (Generic, ToJSON)

If we now run the application again, but forget to update the Elm code, the User decoder will fail at runtime in Elm.

The solution

haskell-to-elm solves this problem by letting us generate the Elm User type and decoder from the Haskell User type.

With haskell-to-elm as part of your build pipeline you can make sure that the frontend is always in sync with your backend, and get type errors in your frontend code when you change your backend types.

The companion library servant-to-elm also lets you generate Elm client libraries for your Servant APIs.

Using DerivingVia to reduce boilerplate

We can use the DerivingVia extension to reduce some of the boilerplate that this library requires. This requires GHC version >= 8.8, because earlier versions had a bug that prevented it to work.

In this file we define a type called ElmType that we derive both the haskell-to-elm and Aeson classes through.

After having defined that type, the code for User is simply:

data User = User
  { _name :: Text
  , _age :: Int
  } deriving (Generic, SOP.Generic, SOP.HasDatatypeInfo)
    deriving (Aeson.ToJSON, Aeson.FromJSON, HasElmType, HasElmDecoder Aeson.Value, HasElmEncoder Aeson.Value) via ElmType "Api.User.User" User

This also means that we can ensure that we pass the same Aeson options to this library’s Elm code generation functions and Aeson’s JSON derivation functions, meaning that we don’t risk mismatched JSON formats.

Libraries that use or are used by haskell-to-elm:

  • elm-syntax defines Haskell ASTs for Elm’s syntax, and lets us pretty-print it.
  • servant-to-elm can be used to generate Elm client libraries from Servant APIs.
  • haskell-to-elm-test does end-to-end testing of this library.

Others:


Articles

  • coming soon...