Logo

0x3d.Site

is designed for aggregating information.
Welcome
check repository here

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.

Basic usage

To generate code for the User type above, we first need to derive a bunch of class instances:

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

instance HasElmType User where
  elmDefinition =
    Just $ deriveElmTypeDefinition @User defaultOptions "Api.User.User"

instance HasElmDecoder Aeson.Value User where
  elmDecoderDefinition =
    Just $ deriveElmJSONDecoder @User defaultOptions Aeson.defaultOptions "Api.User.decoder"

instance HasElmEncoder Aeson.Value User where
  elmEncoderDefinition =
    Just $ deriveElmJSONEncoder @User defaultOptions Aeson.defaultOptions "Api.User.encoder"

Then we can print the generated Elm code using the following code:

main :: IO ()
main = do
  let
    definitions =
      Simplification.simplifyDefinition <$>
        jsonDefinitions @User

    modules =
      Pretty.modules definitions

  forM_ (HashMap.toList modules) $ \(_moduleName, contents) ->
    print contents

Running main will print the following Elm code:

module Api.User exposing (..)

import Json.Decode
import Json.Decode.Pipeline
import Json.Encode


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


encoder : User -> Json.Encode.Value
encoder a =
    Json.Encode.object [ ("name" , Json.Encode.string a.name)
    , ("age" , Json.Encode.int a.age) ]


decoder : Json.Decode.Decoder User
decoder =
    Json.Decode.succeed User |>
    Json.Decode.Pipeline.required "name" Json.Decode.string |>
    Json.Decode.Pipeline.required "age" Json.Decode.int

In an actual project we would be writing the code to disk instead of printing it.

See this file for the full code with imports.

Parameterised types

Since version 0.3.0.0, haskell-to-elm supports generating code for types with type parameters.

For example, let's say we have the following Haskell type:

data Result e a
  = Err e
  | Ok a
  deriving (Generic, Aeson.ToJSON, SOP.Generic, SOP.HasDatatypeInfo)

We can derive the corresponding Elm type and JSON encoders and decoder definitions with the following code:

instance HasElmType Result where
  elmDefinition =
    Just $ deriveElmTypeDefinition @Result defaultOptions "Api.Result.Result"

instance HasElmDecoder Aeson.Value Result where
  elmDecoderDefinition =
    Just $ deriveElmJSONDecoder @Result defaultOptions Aeson.defaultOptions "Api.Result.decoder"

instance HasElmEncoder Aeson.Value Result where
  elmEncoderDefinition =
    Just $ deriveElmJSONEncoder @Result defaultOptions Aeson.defaultOptions "Api.Result.encoder"

For parameterised types we also have to add instances for how to handle the type when it's fully applied to type arguments. Like this:

instance (HasElmType a, HasElmType b) => HasElmType (Result a b) where
  elmType =
    Type.apps (elmType @Result) [elmType @a, elmType @b]

instance (HasElmDecoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmDecoder Aeson.Value (Result a b) where
  elmDecoder =
    Expression.apps (elmDecoder @Aeson.Value @Result) [elmDecoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]

instance (HasElmEncoder Aeson.Value a, HasElmDecoder Aeson.Value b) => HasElmEncoder Aeson.Value (Result a b) where
  elmEncoder =
    Expression.apps (elmEncoder @Aeson.Value @Result) [elmEncoder @Aeson.Value @a, elmDecoder @Aeson.Value @b]

The rationale for having two instances of the classes for each type is that we both have to describe how the type is defined (with the unapplied instances), which generates parameterised definitions, and then we describe how to actually use those parameterised definitions with the applied instances.

These instances print the following code when run:

module Api.Result exposing (..)

import Json.Decode
import Json.Decode.Pipeline
import Json.Encode


type Result a b
    = Err a
    | Ok b


encoder : (a -> Json.Encode.Value) -> (b -> Json.Encode.Value) -> Result a b -> Json.Encode.Value
encoder a b c =
    case c of
        Err d ->
            Json.Encode.object [ ("tag" , Json.Encode.string "Err")
            , ("contents" , a d) ]

        Ok d ->
            Json.Encode.object [ ("tag" , Json.Encode.string "Ok")
            , ("contents" , b d) ]


decoder : Json.Decode.Decoder a -> Json.Decode.Decoder b -> Json.Decode.Decoder (Result a b)
decoder a b =
    Json.Decode.field "tag" Json.Decode.string |>
    Json.Decode.andThen (\c -> case c of
        "Err" ->
            Json.Decode.succeed Err |>
            Json.Decode.Pipeline.required "contents" a

        "Ok" ->
            Json.Decode.succeed Ok |>
            Json.Decode.Pipeline.required "contents" b

        _ ->
            Json.Decode.fail "No matching constructor")

Notice that the generated encoder and decoder are parameterised by the encoder and decoder for the type arguments.

See this file for the full code with imports.

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.

Roadmap

  • Derive JSON encoders and generically
  • Pretty-print the Elm AST
    • Separate pretty printing from code generation: elm-syntax
  • Generate Elm modules
  • Servant client library generation: servant-to-elm
  • Test that encoding and decoding round-trip: haskell-to-elm-test
  • Support parameterised types

Related projects

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:

Elm
Elm
Elm is a functional programming language designed for building front-end web applications. It emphasizes simplicity, performance, and reliability, producing no runtime errors. Elm is ideal for creating robust user interfaces.
compiler/hints at master · elm/compiler
compiler/hints at master · elm/compiler
GitHub - rogeriochaves/spades: Start an Elm SPA ready to the real world
GitHub - rogeriochaves/spades: Start an Elm SPA ready to the real world
GitHub - huytd/kanelm: Kanban board built with Elm
GitHub - huytd/kanelm: Kanban board built with Elm
GitHub - lydell/elm-watch: `elm make` in watch mode. Fast and reliable.
GitHub - lydell/elm-watch: `elm make` in watch mode. Fast and reliable.
GitHub - stereobooster/type-o-rama: 👾 JS type systems interportability
GitHub - stereobooster/type-o-rama: 👾 JS type systems interportability
GitHub - tarbh-engineering/journal: The secure, private journal.
GitHub - tarbh-engineering/journal: The secure, private journal.
GitHub - ashellwig/generator-elm-mdl: Yeoman generator for elm-mdl in pure elm
GitHub - ashellwig/generator-elm-mdl: Yeoman generator for elm-mdl in pure elm
GitHub - elmariofredo/elm-hn-pwa: Hacker News as a PWA built with Elm
GitHub - elmariofredo/elm-hn-pwa: Hacker News as a PWA built with Elm
GitHub - simonewebdesign/elm-new: 💾 Generate a new Elm project from the command line (Elm 0.16+)
GitHub - simonewebdesign/elm-new: 💾 Generate a new Elm project from the command line (Elm 0.16+)
GitHub - stil4m/elm-analyse: A tool that allows you to analyse your Elm code, identify deficiencies and apply best practices.
GitHub - stil4m/elm-analyse: A tool that allows you to analyse your Elm code, identify deficiencies and apply best practices.
GitHub - pzp1997/elm-ios: Bringing the wonders of Elm to the iOS platform
GitHub - pzp1997/elm-ios: Bringing the wonders of Elm to the iOS platform
GitHub - agrafix/elm-bridge: Haskell: Derive Elm types from Haskell types
GitHub - agrafix/elm-bridge: Haskell: Derive Elm types from Haskell types
articles/switching_from_imperative_to_functional_programming_with_games_in_Elm.md at master · Dobiasd/articles
articles/switching_from_imperative_to_functional_programming_with_games_in_Elm.md at master · Dobiasd/articles
GitHub - evancz/elm-todomvc: The TodoMVC app written in Elm, nice example for beginners.
GitHub - evancz/elm-todomvc: The TodoMVC app written in Elm, nice example for beginners.
GitHub - jschomay/elm-narrative-engine: A tool for building interactive fiction style stories in Elm.
GitHub - jschomay/elm-narrative-engine: A tool for building interactive fiction style stories in Elm.
GitHub - rtfeldman/grunt-elm: Grunt plugin that compiles Elm files to JavaScript.
GitHub - rtfeldman/grunt-elm: Grunt plugin that compiles Elm files to JavaScript.
GitHub - jfairbank/run-elm: Run Elm code from the command line
GitHub - jfairbank/run-elm: Run Elm code from the command line
GitHub - JustusAdam/elm-init: Initialise scaffolding for a new Elm project
GitHub - JustusAdam/elm-init: Initialise scaffolding for a new Elm project
GitHub - halfzebra/create-elm-app: 🍃 Create Elm apps with zero configuration
GitHub - halfzebra/create-elm-app: 🍃 Create Elm apps with zero configuration
GitHub - halfzebra/elm-examples: :book: Practical examples in Elm
GitHub - halfzebra/elm-examples: :book: Practical examples in Elm
GitHub - khusnetdinov/elmkit: :school_satchel: Elm kit is web application boilerplate kit for development. This kit build on Brunch, Node, Sass, Elm-lang. It helps you to start development more productive following best practices.
GitHub - khusnetdinov/elmkit: :school_satchel: Elm kit is web application boilerplate kit for development. This kit build on Brunch, Node, Sass, Elm-lang. It helps you to start development more productive following best practices.
GitHub - rofrol/elm-games: All Elm Games (hopefully)
GitHub - rofrol/elm-games: All Elm Games (hopefully)
GitHub - Chadtech/elmish-wasm: Experiment to compile something Elm-ish to Wasm
GitHub - Chadtech/elmish-wasm: Experiment to compile something Elm-ish to Wasm
GitHub - robertjlooby/elm-koans: A set of koans for learning Elm
GitHub - robertjlooby/elm-koans: A set of koans for learning Elm
Rework Vim install instructions · Issue #610 · avh4/elm-format
Rework Vim install instructions · Issue #610 · avh4/elm-format
GitHub - evancz/elm-architecture-tutorial: How to create modular Elm code that scales nicely with your app
GitHub - evancz/elm-architecture-tutorial: How to create modular Elm code that scales nicely with your app
GitHub - brian-watkins/elm-spec: Describe the behavior of Elm programs
GitHub - brian-watkins/elm-spec: Describe the behavior of Elm programs
GitHub - zwilias/elm-json: Install, upgrade and uninstall Elm dependencies
GitHub - zwilias/elm-json: Install, upgrade and uninstall Elm dependencies
GitHub - gicentre/litvis: Literate Visualization: Theory, software and examples
GitHub - gicentre/litvis: Literate Visualization: Theory, software and examples
GitHub - eeue56/elm-for-web-developers: A collection of tips for people using Elm from a web-dev background
GitHub - eeue56/elm-for-web-developers: A collection of tips for people using Elm from a web-dev background
Elm
More on Elm

Programming Tips & Tricks

Code smarter, not harder—insider tips and tricks for developers.

Error Solutions

Turn frustration into progress—fix errors faster than ever.

Shortcuts

The art of speed—shortcuts to supercharge your workflow.
  1. Collections 😎
  2. Frequently Asked Question's 🤯

Tools

available to use.

Made with ❤️

to provide resources in various ares.