GithubHelp home page GithubHelp logo

elm-export's Introduction

Elm Export

Build Status

Create Elm classes and JSON decoders from Haskell DataTypes.

Installation

Elm Export is available on Hackage.

Usage

To use this library, you must first make the types you want to export implement ElmType. This is easy. Just derive Generic, and then we can automatically generate the ElmType instance for you. Here's an example with a Person type:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

module Db where

import Elm
import GHC.Generics

data Person = Person
  { id :: Int
  , name :: Maybe String
  } deriving (Show, Eq, Generic, ElmType)

That's it for the type. Now you'll want to write a main that generates the Elm source code:

module Main where

import Data.Proxy
import Db
import Elm

spec :: Spec
spec =
  Spec
    ["Db", "Types"]
    [ "import Json.Decode exposing (..)"
    , "import Json.Decode.Pipeline exposing (..)"
    , toElmTypeSource (Proxy :: Proxy Person)
    , toElmDecoderSource (Proxy :: Proxy Person)
    ]

main :: IO ()
main = specsToDir [spec] "some/where/output"

Run this and the directory some/where/output will be created, and under that the Elm source file Db/Types.elm will be found.

All the hard work here is done by toElmTypeSource and toElmDecoderSource. The Spec code is just wrapping to make it easy to create a complete Elm file from the meat that ElmType gives you.

Required Elm Packages

The decoders we produce require these extra Elm packages installed:

elm package install NoRedInk/elm-decode-pipeline
elm package install krisajenkins/elm-exts

Development

You will need Stack.

Building

stack build

Testing

stack test --file-watch

Change Log

V0.3.0.0

  • Renamed ToElmType to ElmType, for brevity.

V0.2.0.0

V0.1.0.0

  • Initial release.

Status

Alpha. The author is using it in production, but it is not yet expected to work for every reasonable case.

There are some Haskell datatypes that cannot be represented in Elm. Obviously we will not support those. But there are some which are legal Haskell and legal Elm, but we do not yet generate. Please send examples, PRs and code-suggestions!

Contributors

License

Copyright © 2015-2017 Kris Jenkins

Distributed under the Eclipse Public License.

See Also

Elm Bridge is a different implementation of the same goal. That project uses Template Haskell, this one uses GHC Generics.

elm-export's People

Contributors

apatil avatar domenkozar avatar infernalknight avatar krisajenkins avatar mattjbray avatar nomicflux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

elm-export's Issues

Support for algebraic sum types

I raised this issue a couple days ago with @mattjbray on his servant-elm package, and he directed me here, since servant-elm uses a fork of elm-export under the hood.

Basically, I'd love to see support for algebraic sum types in elm-export. I'd like to be able to get an ElmType instance for something like data Position = Beginning | Middle | End deriving (Eq, Generic)

As an aside, your presentation on Elm at Red Badger was what convinced me to give Elm a go, so thanks for that!

Pattern match(es) are non-exhaustive

I was getting a runtime (!) exception with the following;

myApp: src/Elm/Record.hs:(12,1)-(67,23): Non-exhaustive patterns in function render

Pulling down the elm-export source and building;

$ stack build --pedantic

elm-export/src/Elm/Encoder.hs:13:1: warning: [-Wincomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘render’:
        Patterns not matched:
            (TopLevel (TopLevel _))
            (TopLevel (Record _ _))
            (TopLevel (Constructor _ _))
            (TopLevel (Selector _ _))
            ...

elm-export/src/Elm/Record.hs:12:1: warning: [-Wincomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘render’:
        Patterns not matched:
            (TopLevel (TopLevel _))
            (TopLevel (DataType _ (TopLevel _)))
            (TopLevel (DataType _ (DataType _ _)))
            (TopLevel (DataType _ (Constructor _ _)))
            ...

I tried fixing it but couldn't quite grok things.

Adding render _ = return "" to both Encoder and Record files "did the trick" in terms of the runtime exception in my dependant app, but I doubt that's the Right™ fix.

Would be happy to submit a PR with some direction 😄

Invalid decoder for `Map` (dictionary) field

Given the Haskell data type

data Foo = Foo { foo :: Map String Int }

An example response from Aeson (via Servant) is

{
    "foo": {
        "1": 1,
        "2": 2
    }
}

However, the decoder for Foo generated by elm-export is the following:

decodeFoo : Decoder Foo
decodeFoo =
    decode Foo
        |> required "foo" (map Dict.fromList (list (map2 (,) (index 0 string) (index 1 int))))

i.e. it's expecting a list, not an object. This mismatch can be verified by actually attempting to decode data from Elm:

"Expecting a List at _.foo but instead got: {\"1\":1,\"2\":2}"

I have created a repository with a self-contained program that can be used to reproduce the issue here: https://github.com/ehamberg/elm-export-test

I have tested this on version 0.6.0.1 and devel@ 7ca89b88393925ebd27ace8c919cec1fe0f511c0.

Invalid encoder for `Date`

I think the JSON encoder for Date is not compatible with Aeson. It uses the toString method which produces

(elm repl)
> toString (Date.fromTime 1491938767000)
"<Tue Apr 11 2017 12:26:07 GMT-0700 (PDT)>" : String

but aeson is expecting an ISO8601 date
https://hackage.haskell.org/package/aeson-1.1.1.0/src/Data/Aeson/Parser/Time.hs#day

(ghci)
Prelude> :set -XOverloadedStrings
Prelude> import Data.Time.Calendar as D
Prelude D> import Data.Aeson as A
Prelude D A> fmap D.toGregorian (A.decode "\"2017-01-01\"")
Just (2017,1,1)

I'm happy to submit a pull request.

Update for Elm 0.18.

Todo:

  • Merge/fix anything outstanding.
  • Publish the last of the 0.17 series, for anyone that wants it.
  • Update to 0.18.

State of the project

Hey,

I’ve hacking and contributing around on https://github.com/FPtje/elm-export. I think it’d be a good thing to merge its master into yours. If you don’t feel you want to maintain the project anymore, maybe it’d be a good thing to add a few more contributors so that the project can still live on?

A few things I added lately:

  • NonEmpty and Natural support.
  • Fix for some decoders
  • I’m currently writing a patch so that UTCTime get their encoders / decoders Em part.

Document Elm requirements

When trying out the Person example in the readme, I stumbled upon some errors thrown by the generated code. Symbols like required were not found. When I checked out the documentation of the core library, I was surprised to find that required was never a thing in Json.Decode.

After a while I figured out that it's a function from a separate library: elm-decode-pipeline. The example also imports Json.Decode.Extra, which is from the package elm-json-extra.

It's probably a good idea to document these dependencies, as it would make it easier to get a working prototype.

Edit: This also means the example contains an error, as it doesn't import Json.Decode.Pipeline.

Can't derive encoders for records with modified field names

In short, these are incompatible:

toElmTypeSourceWith (defaultOptions {fieldLabelModifier = withPrefix "post"}
toElmEncoderSourceWith (defaultOptions {fieldLabelModifier = withPrefix "post"}

Simple workaround I have used is:

--- a/src/Elm/Encoder.hs
+++ b/src/Elm/Encoder.hs
@@ -44,7 +44,7 @@ instance HasEncoder ElmValue where
     valueBody <- render value
     return . spaceparens $
       dquotes (stext (fieldModifier name)) <> comma <+>
-      (valueBody <+> "x." <> stext name)
+      (valueBody <+> "x." <> stext (fieldModifier name))
   render (ElmPrimitiveRef primitive) = renderRef primitive
   render (ElmRef name) = pure $ "encode" <> stext name
   render (Values x y) = do

But then this changes semantics and in't compatible with existing encoders.

Types of kind * -> *

Are parametrized types supported? E.g. How would I turn a Haskell type

data Tree a = Leaf a | Node (Tree a) (Tree a)
  deriving (Generic, ElmType)

into an Elm declaration

type Tree a = Leaf a | Node (Tree a) (Tree a)

?

Consider using generics-sop

@kosmikus gave very good talk at ZuriHac 2016 about it: https://www.youtube.com/watch?v=sQxH349HOik

I personally also use it to write generic code, and after you get familiar with the combinators (i.e. don't recurse yourself) code might start seeing magical. For example I generate swagger schema type (i.e. the very related problem this package solves), only for record-like types though: https://github.com/futurice/haskell-futurice-prelude/blob/9c4c13e6da9142e4a4b246ef3b8b189fbc4e160f/src/Futurice/Generics.hs#L216-L248

Error while encoding a newtype

newtype Email = Email Text deriving (Eq, Show, Read, Generic)
deriving instance ElmType Email

-- [snip]

specs :: [Spec]
specs =
  [
    Spec ["AutoGenerated", "Api"]
    ( defElmImports
      : toElmTypeSource (Proxy :: Proxy Types.Email)
      : toElmDecoderSource (Proxy :: Proxy Types.Email)
      : toElmEncoderSource (Proxy :: Proxy Types.Email)
    )
  ]

Error:

Prelude Elm CodeGen> specsToDir specs "/tmp/elm"
Writing: /tmp/elm/AutoGenerated/Api.elm
*** Exception: src/Elm/Encoder.hs:(37,3)-(39,67): Non-exhaustive patterns in function render

Support for optional fields

Hello, first I would like to say thanks for this awesome module. It was very easy to get going for the most basic case where the JSON field names match record names, and all fields are required.

I quickly ran into issues where I had types with slightly more complex serializers/deserializers. For example, both Aeson and Elm allow for optional JSON fields given a default value:

data Person = Person 
    { noteid :: String
    , name :: String
    , birth :: String
    , gender :: String
    }

instance FromJSON Person where
  parseJSON = withObject "person" $ \o -> do
    id <- o .: "id"
    name <- o .:? "name" .!= "unknown"
    birth <- o .:? "birth" .!= "unknown"
    gender <- o .:? "gender" .!= "unknown"
    return Person{..}
decodePerson : Decoder Person
decodePerson =
    decode Person
        |> required "id" string
        |> optional "name" string "unknown"
        |> optional "birth" string "unknown"
        |> optional "gender" string "unknown"

It seems to me that it should be possible to create custom instances for more complex cases as you can do in Aeson. I can work around this by writing the Elm decoder by hand (in Elm), but I think that there is value in having all of my types and deserializers/serializers defined in the same place, and compiled in the same step. What are your thoughts?

elm-export 0.6.0.0 generates wrong Elm ref

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE DeriveAnyClass     #-}
{-# LANGUAGE OverloadedStrings     #-}

import Data.Proxy
import Elm
import GHC.Generics

data Foo = Foo { getIt :: String} deriving (Generic, ElmType)

main :: IO ()
main = print $ toElmTypeRef (Proxy :: Proxy (String, Foo))

Prints "( String, type alias Foo =\n { getIt : String\n } )"

while it worked correctly with 0.5.0.2, I wonder what commit is used for 0.6.0.0?

cc @krisajenkins

Release to Hackage

@krisajenkins: I'd like to encourage you to release this to hackage in it's current state. It would make it slightly easier to use and -- more importantly -- more discoverable. Hackage makes no promises regarding stability or maturity. (You could add stability: alpha to the cabal file to make the status of the project more explicit.)

Export internal types

I find I'm hacking a lot of stuff into a local elm-export repo and building that in parallel with my project. Going forward, it seems likely that people may have conflicting ideas about how to map Haskell types to Elm types and back (e.g. for dates, container types) and may want to add Haskell types from other obscure libraries for a particular project. You can't do this now because none of the internals are available.

Perhaps an Elm.Internal module could be added with the usual caveats about changes, so that users can add these customizations to their own projects if they wish.

Upgrade to Elm 0.19

Hi, is there anyone working on upgrading elm-export to Elm 0.19? If so what is the status, and can I help? :)

Improved pretty-printing

Let's try ansi-wl-pprint and see if it makes the pretty-printing less of a hit & miss affair. :-D

HasDecoder instance for ElmConstructor is partial

Hi, I don't know much about Elm yet, but I put something together that hit an non-exhaustive pattern match panic - there's no MultipleConstructors pattern for rendering an ElmConstructor.

Does this have to do with #6?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.