Developing further on the previous post, building some slightly less trivial examples with the combination of servant-elm and persistent (with Sqlite backend) yields some interesting problems in the generated Elm code.
1. Phantom Types for Keys
Due to the way persistent handles database primary keys, servant-elm (via the underlying elm-bridge) currently generates Elm code with types such as Key World instead of WorldId, where, for Sqlite, ID variables are of type Int, used for database primary keys.
But what is Key World in terms of Elm types? And what happens when you try to sequence a JSON encoder / decoder for Key (which doesn't exist!), along with a JSON encoder / ecoder for World -- something altogether different from the desired Int?
{ userEnv: (Key World)
, userName: String
[...]
jsonEncUser : User -> Value
jsonEncUser val =
Json.Encode.object
[ ("userEnv", (jsonEncKey (jsonEncWorld)) val.userEnv)
, ("userName", Json.Encode.string val.userName)
2. Missing URL String Encoders
I also couldn't get servant-elm to generate correct URL string encoders, despite defining instances of toHttpApiData, which seems like it should be the thing to do.
[...]
, url =
Url.Builder.crossOrigin "http://localhost:8080"
[ "cellColor"
, (capture_worldid |> String.fromInt)
, (capture_x |> String.fromInt)
, (capture_y |> String.fromInt)
, (capture_color |> String.fromInt)
]
[...
... where Color is defined on the Haskell side as:
= White
| Yellow
| Red
| Green
| Blue
| Grey
deriving (Show, Read, Eq, Generic, Enum, Bounded)
instance FromHttpApiData Color where
parseUrlPiece :: T.Text -> Either T.Text Color
parseUrlPiece = myParse
instance ToHttpApiData Color where
toUrlPiece = T.pack . show
toQueryParam = T.pack . show
Wrapping Up
With the above edits, the Elm code gen + persistence layer stack works again. There was a lot more research and work(arounds) required this time, and I needed some help from friends at the Recurse Center, but it still (mostly!) satisfies the property of writing data definitions only once for the whole stack. It's not hard to see that some of the type mapping issues across languages and data boundaries are non-trivial to resolve, so I hope more usage and development goes into these kinds of type-safe stacks.