elm-bridge icon indicating copy to clipboard operation
elm-bridge copied to clipboard

General questions about design of elm-bridge

Open saurabhnanda opened this issue 7 years ago • 18 comments
trafficstars

I'm extending this library to auto-generate Elm API wrappers based on Servant type-signatures [1]. I've pretty much wrapped my head around the internals, but am scratching my head over the following:

  1. The philosophical / conceptual difference between ETypeDef and EType. It seems to me that EType is some sort of Elm-compatible subset of TH.Type, but I'm not so sure about ETypeDef.
  2. deriveElm uses TH to generate instances for IsElmDefinition. However, there are no IsElmDefinition instances for things like String or Bool. It this because you don't want to have "global" Haskell <=> Elm type-mappings forced upon users of this library, allowing them, instead, to make a choice on a case-by-case basis by using makeModuleContentWithAlterations infra?
  3. Even then, if as an end-user, I want to define an IsElmDefinition for my app, how would I do that for something like String / Bool? How does one write a sensible implementation for compileElmDef :: Proxy a -> ETypeDef? What value should this function return? The only possible option is ETypePrimAlias, but I'm not sure how that would work? (this should give you some context about why I'm asking the very first question).

[1] Btw, are you open to a PR for this functionality? I don't see the point in releasing yet another elm <=> haskell library.

saurabhnanda avatar Oct 24 '18 11:10 saurabhnanda

  1. ETypes represent "type signatures", whereas ETypeDef represent what the time is.

  2. This library tries to write code for the user's type. The rationale was that when an endpoint returns Bool, the user would just use the decoder from Elm instead of generated code. Of course this doesn't work too well when generating code for whole APIs. ...

  3. For your particular problem, I am afraid there is no great solution. It is indeed ETypePrimAlias that makes sense here, and it should alias to String and Bool on the Elm side.

I don't know if this terse answers help you, and I am personally open to PR. I am sure @agrafix is too :) I think this library might need a good rework to suit your needs though ...

One thing that would be nice would be a generic Elm AST pretty printer, instead of the terrible string manipulation that happen all over the place. Would you have that in your sleeves? :)

bartavelle avatar Oct 25 '18 15:10 bartavelle

@bartavelle Thanks for the clarification on ETypeDef vs EType. I'll rephrase my understanding again:

  • EType is trying to describe the structure of a new type that needs to be "written / generated" on the Elm side
  • ETypeDef is trying to specify which existing Elm type should correspond to a given Haskell type

A related question: any reason why EAlias and ESum contain only a subset of the fields of Aeson.Options? Isn't it better to simply have the entire Aeson.Options record as one of the fields?

So, conceptually, here's what is required (or may already be existing):

  1. A way to specify Haskell <=> Elm type correspondence
  2. A way to specify Elm-type <=> Elm-json-codec correspondence
  3. A way to specify which "new" types need to be generated on the Elm side (basically custom Haskell types that do not correspond to anything idiomatic / standard on the Elm side)
  4. A way to specify which new Elm JSON codecs need to be generated

saurabhnanda avatar Oct 26 '18 06:10 saurabhnanda

One thing that would be nice would be a generic Elm AST pretty printer, instead of the terrible string manipulation that happen all over the place. Would you have that in your sleeves? :)

This is probably the last thing that I'll attack. Upon a cursory glance, I wasn't able to find a "blessed" Elm code generator. Also, I'm also internally debating if it is better to stick to string-based templates for Elm code-gen to allow library users to modify them easily. For example, users might want an easy way to modify the Elm api-wrappers that will be generated.

saurabhnanda avatar Oct 26 '18 06:10 saurabhnanda

For the option records, it is probably that those are parameters that have been added recently (or at least, recently enough), and that they are not supported by the library yet.

bartavelle avatar Oct 26 '18 14:10 bartavelle

As for the difference between ETypeDef and EType I would have to read the code to tell you their exact meaning, so your guess is as good as mine right now ;)

bartavelle avatar Oct 26 '18 14:10 bartavelle

I think I found something for safer Elm code gen - http://hackage.haskell.org/package/language-elm

saurabhnanda avatar Oct 27 '18 18:10 saurabhnanda

That would be perfect!

bartavelle avatar Oct 29 '18 10:10 bartavelle

@saurabhnanda we're also interested into this, alongside with https://www.reddit.com/r/haskell/comments/9zq14v/state_of_servantelm/

domenkozar avatar Nov 24 '18 21:11 domenkozar

@domenkozar Could you check out the issue I opened here? https://github.com/agrafix/elm-bridge/issues/41

I'm not too familiar with how people are using elm-export or elm-bridge, and what their short comings are, but it's possible a sturdier core could help move things along.

mitchellwrosen avatar Nov 29 '18 20:11 mitchellwrosen

@agrafix @bartavelle is it alright if we use this thread to discuss evolution of this library?

I've gotten a basic POC of using Servant.Foreign to generate an Elm API for a given Servant API. However, I am unable to come up with a good analog for the ToHttpApiData & FromHttpApiData type-classes of Servant. IIUC any type being "injected" in the URL (either as a path-segment OR as a query-param) needs to implement these type-classes to convert values of that type to/from Text.

How does one implement something like this, fairly automagically on the Elm side? Challenges:

  • In Elm 0.19 toString has been moved to Debug and will not be available once you build the Elm project for production. Otherwise one possible solution could've been to use toString -- but it may not parse correctly on the Haskell side.
  • Use a "helper module" on the Elm side which implements toUrlSegmentBool, toUrlSegmentDouble, toUrlSegmentInt, toUrlSegmentTime, toUrlSegmentUTCTime, etc. But this would need to be manually kept in sync with Haskell code at http://hackage.haskell.org/package/http-api-data-0.4/docs/src/Web.Internal.HttpApiData.html#ToHttpApiData - which mostly uses show instances for all the common types.
  • What about custom types, especially newtypes? In our code, all primary keys are a newtype, eg. newtype PK a = PK Int, and they're using in URL segments a lot. GET /users/:userId. How many different toUrlSegment* functions, on the Elm side, would one need to define to handle something like this?

/cc @domenkozar @mitchellwrosen

saurabhnanda avatar Dec 11 '18 09:12 saurabhnanda

servant-elm currently does the following...

https://github.com/mattjbray/servant-elm/blob/9c8a6c289877408581462de9e87701b46832cf02/src/Servant/Elm/Internal/Generate.hs#L431-L440

... but has implemented a hard-coded list of type <=> string conversion functions for 0.19:

https://github.com/mattjbray/servant-elm/pull/45/files#diff-7e13462cc66d19b6a0d2a0ddcd7e8864R494

saurabhnanda avatar Dec 11 '18 09:12 saurabhnanda

Okay! I've got a PoC in place which can do the following...

...given the following Servant API...

data Routes route = Routes
  { rRunCode :: route :- "runCode" :> "randomUrlSegment" :> Capture "id" Int :> Capture "order" String :> QueryParam "tryingEither" (Either Int Bool) :> QueryParam "someOtherParam" Bool :> ReqBody '[JSON] (Maybe InterpreterInput) :> Post '[JSON] InterpreterOutput
  } deriving (Generic)

... it generates the following Elm API wrapper...

postrunCoderandomUrlSegmentbyidbyorder : Msg -> Int -> String -> Maybe (Either (Int) (Bool)) -> Maybe (Bool) -> Maybe (InterpreterInput ) -> Cmd Msg
postrunCoderandomUrlSegmentbyidbyorder msg0 id3 order4 tryingEither5 someOtherParam6 body8 = 
  Http.post { url = Url.absolute ["runCode", "randomUrlSegment", String.fromInt id3, identity order4] <| List.filterMap identity [toUrlSegmentMaybe (toUrlSegmentEither (String.fromInt) (toUrlSegmentBool)) tryingEither5, toUrlSegmentMaybe (toUrlSegmentBool) someOtherParam6], body = Http.jsonBody <| jsonEncMaybe (jsonEncInterpreterInput) body8, expect = Http.expectJson msg0 jsonDecInterpreterOutput}

Shall I bring this into a PR-able form? Are you alright with introducing a dependency on servant?

saurabhnanda avatar Dec 11 '18 15:12 saurabhnanda

I personally use servant anyway, and I suppose most people do (?). It would be nice to ask @agrafix though, as he perhaps still uses this project?

bartavelle avatar Dec 12 '18 09:12 bartavelle

@agrafix has been awfully quiet lately. Is he online on IRC / Reddit these days?

saurabhnanda avatar Dec 12 '18 10:12 saurabhnanda

RE: Elm and http-api-data

I have hit the same issue and it's possible to use generics, but as soon as there is a manual instance in Haskell, something like http://blog.stermon.com/articles/2018/04/09/elm-stringeable-types-library-for-elm-019.html would work.

domenkozar avatar Dec 12 '18 10:12 domenkozar

No idea! To be honest, I do not use this package anymore either, as I have stopped using Elm. I will try to keep maintaining it, and have a couple things to do on it, but this will have to wait for January ...

bartavelle avatar Dec 12 '18 10:12 bartavelle

@bartavelle Moved on to PureScript? :)

mitchellwrosen avatar Dec 12 '18 17:12 mitchellwrosen

@mitchellwrosen moved out of frontend, but last personal project was purescript + wasm indeed ;)

bartavelle avatar Dec 12 '18 17:12 bartavelle