adl
adl copied to clipboard
Full Example for type alias custom type
The documentation mentions that we can have the following ADL type:
newtype Date = String;
be converted to the following Haskell type:
type Date = Day
Can you please provide a full example?
I've tried this:
@HaskellCustomType {
"haskellname" : "Data.Time.Calendar.Day",
"haskellimports" : ["qualified Data.Time.Calendar"],
"insertCode" : ["type Date = Data.Time.Calendar.Day"],
"generateOrigADLType" : "",
"structConstructor" : "Dummy",
"unionConstructors" : []
}
newtype Date = String;
But I get the haskell error: No instance for (AdlValue Date)
I think I need to specify the functions for converting Day
to/from a string. But I can't figure this out.
Thank you
You have to write the instance for AdlValue
to implement the de/serialization to/from json.
I'll extend the test to a fully worked example.
Thank you. Do you recommend creating an orphan instance for Data.Time.Calendar.Day
, or is there some way to add the instance into the AdlValue
source file?
You either need to use a newtype or create an orphan instance.
I don't think it would be feasible to inject the value into the Value.hs source file.
Thank you. The "custom types" feature of ADL is amazing and what I believe sets it apart from protocol-buffers and the others (as well as other great ADL features like generics).
But I have 2 suggestions that I think would improve things for custom types:
-
Instead of requiring a type-class for the custom type, in the annotation specify two functions:
toType = "Data.Time.Calendar.Day.fromString" fromType = "Data.Time.Calendar.Day.toString"
The code generator would then directly call those functions inside the generated serialization code. This way, there is no need to deal with newtypes or orphan instances. This is a simple approach that I believe would work great for all languages.
-
The above two functions should have a type
CustomType -> T
andT -> CustomType
, whereT
is the backing ADL type. In the current implementation, custom types serialize to/from JSON. This is unnecessarily tightly coupled to the current serialization ADL JSON implementation. But in the future ADL will (hopefully) develop a binary serialization (and other formats). If custom types serialize to/from the backing ADL type instead of JSON, then they will transparently work with any serialization format.
Thank you
The issue with newtypes and/or orphan instances is annoying - it applies both in the haskell and the rust language backends.
However, the implementation of serialization for generics current relies on type classes/traits so that a single generic serializer works with all instances (including custom types). I think this precludes your suggested approach of
code generator would then directly call those functions inside the generated serialization code
FWIW, in the java and typescript backends we rely on reified instances of interfaces equivalent to the type classes (eg JsonBinding and JsonBinding in json.ts). This approach lacks some of the convenience of using type classes, but avoids the need for newtypes or orphans.
Perhaps the haskell backend could also use a similar approach of reified serializers, but I sure people with then start asking "why aren't you using typeclasses?" .
But in the future ADL will (hopefully) develop a binary serialization
See https://github.com/timbod7/adl/issues/75
I'm not against the idea, just haven't had a driving need. The json serialization has been surprisingly versatile.
Thank you for the response. I will take a look at the Java and TypeScript implementations (But the TypeScript implementation doesn't support custom types yet, right?).
In the meantime I would like to respond to:
Perhaps the haskell backend could also use a similar approach of reified serializers, but I sure people with then start asking "why aren't you using typeclasses?" .
I am not certain that people would ask that. A large portion of the Haskell community is a await of the argument against using typeclasses for serialization. For example, the tomland
TOML serialization library doesn't use typeclasses. They say:
A function-based approach for the description of how to convert Haskell data types to/from TOML instead of a typeclasses-based approach. This approach has a lot of pleasant advantages for decoding libraries: no orphan instances and no ambiguity when there are multiple options for conversion. Here tomland goes along with libraries like hedgehog, sv and waargonaut.
https://kowainik.github.io/posts/2019-01-14-tomland#key-concepts
As mentioned, warrgonaut and sv also are typeclassless and have good explanations about the advantages of a typeclassless approach.
I'm not against the idea, just haven't had a driving need [for binary serialization]. The json serialization has been surprisingly versatile.
I agree that the json serialization works well. But even if we end up staying with JSON, I still believe that the approach of custom types serializing to/from ADL types (instead of JSON) is conceptually cleaner and technically better. For example, ADL has a "ByteVector" type. If I make a custom type "Image" that is backed by an ADL "ByteVector", then in the JSON approach I now need to serialize/deserialize to JSON, but JSON doesn't have builtin support for bytes. So I have to look at the implementation of ADL and see how it encodes this in JSON (I'm guessing it uses something like base64 encoded strings?). So now I have to write my own base64 encoder/decoder (and hope that I don't make a bug and that I implement it exactly the same as ADL). And also I am tightly coupled with the ADL implementation (maybe ADL would like to switch to base122 encoding in the future?). With my approach, to implement my custom type I convert to/from a Haskell "ByteString" (or the equivalent in other languages), which is much simpler and more intuitive in my opinion.
A large portion of the Haskell community is a await of the argument against using typeclasses for serialization. ... As mentioned, warrgonaut and sv also are typeclassless and have good explanations about the advantages of a typeclassless approach
You've convinced me. The current serialization approach in the generated haskell dates back to 2014 (!). I'm not so actively following the haskell ecosystem these days, and I wasn't aware of the typeclassless approach taken by other libraries.
I think it would be better indeed to modify the haskell and rust code generators to use this approach, making them consistent with the other language backends.
But even if we end up staying with JSON, I still believe that the approach of custom types serializing to/from ADL types (instead of JSON) is conceptually cleaner and technically better.
Note that this approach is already supported. As part of the custom type annotation (see generateOrigAdlType), you can request that the original serialization code still be generated. That way you can implement your de/serialization logic for your custom type as a conversion to the generated type, followed by a call to the generated de/serializer.
There is value in having explicit control over the serialization for custom types. We often use this as a means of getting deserialization backwards compatibility. If I have a existing type X
for which we have persisted serialized data, and I want to extend or modify this type in a way that "breaks" the serialization. I can use a custom type definition to provide a deserializer that understand both the old and the new json, and converts either to the new X
type.
I'm not sure if this is related. But I think this would also make it possible to have the builtin ADL types map directly to the Haskell types. For example:
- ADL
Nullable
maps to HaskellNullable
type, which is a newtype aroundMaybe
. - ADL
StringMap
maps to HaskellStringMap
type which is a newtype aroundMap
.
I'm not sure I see the value in these wrappers, they just create extra friction when using them in my code. Would be much nicer if the newtypes were eliminated and I would use Maybe
and Map
directly.
Thank you
I'm not sure if this is related.
It is related. These newtype wrappers existing because
- haskell Maybe has different serializations for adl
Nullable<T>
andsys.types.Maybe<T>
- haskell Map has different serializations for adl
StringMap<V>
andsys.types.Map<K,V>
I would use Maybe and Map directly
Note that you already do use Maybe or Map directly when you write ADL sys.types.Maybe<T>
and sys.types.Map<K,V>
. The wrappers are only required for the Nullable<T>
and StringMap<V>
primitives.
I think it would be better indeed to modify the haskell and rust code generators to use this approach, making them consistent with the other language backends.
I started think about what this would look like, and started prototyping. Let me know what you think on
https://github.com/timbod7/adl/pull/128
Thank you, I will try this out soon :+1:
@timbod7 I looked at #128
This is still incomplete, right? But the JsonBinding
looks good to me
It's not even close to being complete.
This was just an initial hand written attempt to reproduce what the generated code and runtime would look like if the code generator were updated to work this way.