agrafix / elm-bridge

Haskell: Derive Elm types from Haskell types
BSD 3-Clause "New" or "Revised" License
101 stars 27 forks source link

General questions about design of elm-bridge #40

Open saurabhnanda opened 5 years ago

saurabhnanda commented 5 years ago

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.

bartavelle commented 5 years ago
  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? :)

saurabhnanda commented 5 years ago

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

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 commented 5 years ago

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.

bartavelle commented 5 years ago

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 commented 5 years ago

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 ;)

saurabhnanda commented 5 years ago

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

bartavelle commented 5 years ago

That would be perfect!

domenkozar commented 5 years ago

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

mitchellwrosen commented 5 years ago

@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.

saurabhnanda commented 5 years ago

@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:

/cc @domenkozar @mitchellwrosen

saurabhnanda commented 5 years ago

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 commented 5 years ago

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?

bartavelle commented 5 years ago

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?

saurabhnanda commented 5 years ago

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

domenkozar commented 5 years ago

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.

bartavelle commented 5 years ago

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 ...

mitchellwrosen commented 5 years ago

@bartavelle Moved on to PureScript? :)

bartavelle commented 5 years ago

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