Open saurabhnanda opened 6 years ago
Actually, just noticed a related issue. The type definitions and json-codecs of Day
and DiffTime
have not been generated. Is this expected?
There is the Date datatype. If the types do not have a ToJson
instance, this would be real problematic to handle properly, and if they have, the best course of action would be to hardcode them in the Elm code generation and to add helper functions in json-helpers
.
@bartavelle any existing examples that do what you are suggesting?
Also, is there any way to extend the code-gen (without patching this library) to use specific types on the Elm side (along with hand-written json codecs) for specific Haskell types?
Specific examples are all the "built-in" datatypes. You might want to check the default alterations. You then use the *WithAlterations
functions and stack your own modifications by composing your own function with this one. Am I making sense, or this is all a bit fuzzy?
Going through the documentation index I could only find one *WithAlterations
function, i.e. makeModuleContentWithAlterations
-- is this the one that you are referring to?
So, the idea is to call makeElmModuleWithAlterations
instead of makeElmModule
and pass it a lambda that does the ETypeDef -> ETypeDef
mapping. Within the lambda, I'm free to use defaultAlterations
for the types that I don't want to alter. Did I get this correctly?
Yup! You can also do something like this, if you do not want to explicitly reference defaultAlterations
in your own function:
makeElmModuleWithAlterations (myalterations . defaultAlterations)
@bartavelle okay, I got something working, but I'm scratching my head over the difference in behaviour of "top-level" and "nested" types. Here's an example based on Integer
(that is baked into the library, at the moment):
Integer
as top-level type$(Elm.deriveElmDef defaultOptions ''Integer)
putStrLn $ makeElmModule "Foo" [DefineElm (Proxy :: Proxy Integer)]
--
-- output
--
module Foo exposing(..)
import Json.Decode
import Json.Encode exposing (Value)
-- The following module comes from bartavelle/json-helpers
import Json.Helpers exposing (..)
import Dict
import Set
type Integer =
S# Int#
| Jp# BigNat
| Jn# BigNat
jsonDecInteger : Json.Decode.Decoder ( Integer )
jsonDecInteger =
let jsonDecDictInteger = Dict.fromList
[ ("S#", Json.Decode.lazy (\_ -> Json.Decode.map S# (jsonDecInt#)))
, ("Jp#", Json.Decode.lazy (\_ -> Json.Decode.map Jp# (jsonDecBigNat)))
, ("Jn#", Json.Decode.lazy (\_ -> Json.Decode.map Jn# (jsonDecBigNat)))
]
jsonDecObjectSetInteger = Set.fromList []
in decodeSumTaggedObject "Integer" "tag" "contents" jsonDecDictInteger jsonDecObjectSetInteger
jsonEncInteger : Integer -> Value
jsonEncInteger val =
let keyval v = case v of
S# v1 -> ("S#", encodeValue (jsonEncInt# v1))
Jp# v1 -> ("Jp#", encodeValue (jsonEncBigNat v1))
Jn# v1 -> ("Jn#", encodeValue (jsonEncBigNat v1))
in encodeSumTaggedObject "tag" "contents" keyval val
Integer
as a nested typedata MyFoo = MyFoo
{
foo1 :: Text
, foo2 :: Integer
} deriving (Eq, Show, Read)
$(Elm.deriveElmDef defaultOptions ''MyFoo)
putStrLn $ makeElmModule "Foo" [DefineElm (Proxy :: Proxy MyFoo)]
--
-- output
--
module Foo exposing(..)
import Json.Decode
import Json.Encode exposing (Value)
-- The following module comes from bartavelle/json-helpers
import Json.Helpers exposing (..)
import Dict
import Set
type alias MyFoo =
{ foo1: String
, foo2: Int
}
jsonDecMyFoo : Json.Decode.Decoder ( MyFoo )
jsonDecMyFoo =
("foo1" := Json.Decode.string) >>= \pfoo1 ->
("foo2" := Json.Decode.int) >>= \pfoo2 ->
Json.Decode.succeed {foo1 = pfoo1, foo2 = pfoo2}
jsonEncMyFoo : MyFoo -> Value
jsonEncMyFoo val =
Json.Encode.object
[ ("foo1", Json.Encode.string val.foo1)
, ("foo2", Json.Encode.int val.foo2)
]
Basically at the top-level, the defaultAlterations
function is applied, and at nested/recursive levels, the recAlterType
function is applied. And both have different behaviours. Any reason for why they have been designed this way?
Actually, just noticed a related issue. The type definitions and json-codecs of Day and DiffTime have not been generated. Is this expected?
After reading through the code of recAlterType
I now see why this is happening, but I do not understand why the function doesn't "truly" descend a type recursively.
When you derive an elm encoding and generate the corresponding code for a type, it will create code that work on that particular type. You might want to manually write the code for "contained" types, or want to stop at "primitive" types (such as Integer
).
If it worked recursively, you could not do that, and you would end up with unwanted code.
You might want to manually write the code for "contained" types, or want to stop at "primitive" types (such as Integer). If it worked recursively, you could not do that, and you would end up with unwanted code.
Apologies beforehand for pressing this further.
MyFoo
if it is contained within MyBar
vs MyBaz
?(ModuleName, TypeName)
tuple, irrespective of where it is in a nesting/recursive hierarchy? Hum, I am not sure I understood your question, so I will elaborate a bit and you'll tell me what I got wrong.
If you have this on the Haskell side:
data Foo = Foo Bar Baz
When you derive Foo
, the generated elm code will be the Foo
type definition, and the encoding/decoding functions for it. It will not derive code for Bar
or Baz
recursively, allowing you to replace them with your own definitions. If you want them generated, you will have to explicitely derive them and include them in the module.
Did I understand correctly that you are wondering why you have to derive Bar
and Baz
explicitely?
Did I understand correctly that you are wondering why you have to derive Bar and Baz explicitely?
Yes, this is precisely what I'm wondering.
Alright, so the answer is that if it did, that would be very annoying.
The reason is that many types have non-aeson Json instances, or that for some reason the user would like to handle it differently than the Haskell type.
For example, I work with a mixed Elm/JS code base, on a single Haskell backend. Some types must have a shape that I can't reproduce with Aeson, because of backward compatibility and how it is hard to refactor the JS code.
Consider another use-case, which is what I am struggling with currently:
Haskell data-structure:
data User
= User {userCreatedAt :: !UTCTime,
userUpdatedAt :: !UTCTime,
userEmail :: !Types.Email,
userRefreshToken :: !Data.Text.Internal.Text,
userAccessToken :: !Data.Text.Internal.Text,
userTokenExpiresAt :: !UTCTime}
Elm auto-gen hook (to convert UTCTime
to Elm's Date
):
makeModuleContentWithAlterations (myAlterations . defaultAlterations)
[ DefineElm (Proxy :: Proxy User) ]
where
myAlterations = case t of
ETypeAlias (x@EAlias{ea_name=(ETypeName "UTCTime" [])}) -> ETypeAlias (x{ea_name=(ETypeName "Date" [])})
_ -> t
Generated Elm type - notice, that the alterations have had no effect
type alias User =
{ userCreatedAt: UTCTime
, userUpdatedAt: UTCTime
, userEmail: Email
, userRefreshToken: String
, userAccessToken: String
, userTokenExpiresAt: UTCTime
}
This is happening because myAlterations
is called only for the top-level type, User
, passed to makeModuleContentWithAlterations
, not for each field/type in User
.
This is what I was trying to refer to, in my comment - https://github.com/agrafix/elm-bridge/issues/32#issuecomment-398869632
Btw, here's the complicated workaround that I'm being forced to use, currently:
myAlterations t = case t of
ETypeAlias (x@EAlias{ea_fields=fields_}) ->
let modifiedFields = (flip DL.map) fields_ $ \(fname, field_) -> case field_ of
ETyCon (y@ETCon{tc_name="UTCTime"}) -> (fname, ETyCon $ y{tc_name="Date"})
y -> (fname, y)
in ETypeAlias (x{ea_fields=modifiedFields})
_ -> t
Oh right, this is quite annoying indeed!
It is my fault, the documentation is lacking. You can use recAlterType
to have a simpler representation and have it applied recursively.
That would be something like
myAlterations = recAlterType myTypeAlterations
myTypeAlterations t = case t of
ETyCon (ETCon "UTCTime") -> ETyCon (ETCon "Date")
_ -> t
There is also an example in the haddocks.
If it can be of any help, I ended up using a custom type instead https://gist.github.com/ibizaman/efa6ee8a7b4e0f5696c24ff493e5744a
What's the best way to deal with UTCTime? I tried deriving the Elm definition and here's what showed-up on the Elm side:
Is this the idiomatic way of dealing with timestamps on the Elm side? (I'm new to Elm, so please pardon this newbie question). If not, then how does one specify some sort of "mapping" between an idiomatic Haskell type and its corresponding idiomatic Elm counterpart?