-
Notifications
You must be signed in to change notification settings - Fork 17
sum and sumWith (Type safe codecs for sum types with custom encodings)
#62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
sum and sumWithsum and sumWith (Type safe codecs for sum types with custom encodings)
|
Sorry, I meant to get back to this sooner. I was surprised when I saw the PR as I thought I'd already added something very similar to this, as I've already been using it in a project for years! I think I didn't get around to adding it because I my version doesn't take options about the representation, and I had intended to do something like what you have here. I also have mine working without using the There is another encoding representation I'd like to support however, and that's using keys rather than tags. So instead of: { "tag": "Foo", "value": 42 }It'd be: { "Foo": 42 }But I imagine you might not care about that, so if you like we can merge it now and then I'll figure out adding support for this other representation (since it means changing a bunch of stuff including the options representation even - only Also can you delete the spago files for now? I will migrate this to new spago, but want to use version ranges still, and unfortunately I'm not ready to drop bower support yet so would like to do something in CI to ensure they stay in sync or something. |
|
That's a good idea to implement the mentioned encoding as well. I can do it. I'd change the encoding type to:
I am not sure if it's the best naming. Rust's serde library calls those different encodings
See: https://serde.rs/enum-representations.html Since we're at it, there are also 2 more encodings:
Let me know if you have any suggestion towards this. But yeah, as I said, I can keep on working on this. I'll remove the spago files, and I think there's no need to merge now. We can merge, once everything is agreed on. There may be performance optimizations, too. I'll come back to that later. |
|
I was using the term "nested" for I would prefer to omit untagged, and force people to write codecs manually if they want that. One of the goals I had with the library is that if you only use the combinators it provides then the codecs are guaranteed to roundtrip, but that can easily be violated by the untagged representation. Admittedly there are some existing violations of that - the Thinking about it, I'd be tempted to split off a second "unsafe" library that provides things like that that are needed when you're not in control of the serialization format at both ends, but that can't be guaranteed to accurately roundtrip, as I do find myself hand writing codecs at times that could be made generic with some unsafety of that kind. I think I understand what you mean with the internally tagged example, but just to check, it's for when you have something like this? data Something
= CtorA { fieldA :: Int, fieldB :: String }
| CtorB { ... }
| CtorC { ... }Due to the potential for tag-field key collisions as you pointed out that maybe would be another one for the "unsafe" library, but it would be very useful indeed, that's probably the most common form of sum type representation I encounter when it's not in my control. I've found a reasonably okay way of writing those where you leverage type DataIssueFieldsBase r =
{ pipelineId ∷ Pipeline.Id
, datasetId ∷ Dataset.Id
| r
}
type DataIssueFields = DataIssueFieldsBase ()
type MissingColumnFields = DataIssueFieldsBase
( names ∷ NonEmptyArray String
)
data Issue
= MissingRows DataIssueFields
| DuplicateRows DataIssueFields
| MissingColumns MissingColumnFields
codecDataIssueFields ∷ CA.JPropCodec DataIssueFields
codecDataIssueFields = CAR.record
{ pipelineId: Pipeline.codecId
, datasetId: Dataset.codecId
}
codecMissingColumnFields ∷ CA.JPropCodec MissingColumnFields
codecMissingColumnFields =
codecDataIssueFields
# CA.recordProp (Proxy ∷ _ "names") (Codec.nonEmptyArray CA.string)
codec ∷ CA.JsonCodec Issue
codec = C.codec' decode encode
where
encode ∷ Issue → J.Json
encode = J.fromObject ∘ case _ of
MissingRows fields →
Object.fromFoldable
$ ("type" × J.fromString "missing-rows")
: CA.encode codecDataIssueFields fields
DuplicateRows fields →
Object.fromFoldable
$ ("type" × J.fromString "duplicate-rows")
: CA.encode codecDataIssueFields fields
MissingColumns fields →
Object.fromFoldable
$ ("type" × J.fromString "missing-columns")
: CA.encode codecMissingColumnFields fields
decode ∷ J.Json → Either CA.JsonDecodeError Issue
decode j = do
obj ← CA.decode CA.jobject j
typ ← lmap
(CA.AtKey "type")
(CA.decode CA.string =<< note CA.MissingValue (Object.lookup "type" obj))
case typ of
"missing-rows" →
MissingRows <$> CA.decode codecDataIssueFields obj
"duplicate-rows" →
DuplicateRows <$> CA.decode codecDataIssueFields obj
"missing-columns" →
MissingColumns <$> CA.decode codecMissingColumnFields obj
other →
Left (CA.AtKey "type" (CA.UnexpectedValue (J.fromString other)))But we definitely should be able to eliminate the need for that. |
Ok, from my side, this PR would be ready to be merged. Do you have any other points to discuss? |
|
Sorry about the delay, I was on holiday for a couple of days and then catching up with work stuff, etc. This is looking great though, thanks very much for your work on it! |
|
Great to see this merged, no worries about the delay! |
This PR provides a type safe way to define codecs for sum types.
Here we have a sum type with constructors which have different amounts of fields.
The codec gets defined by providing a record with a field for each Sum case. The codecs for the fields themselves get listed with nested tuples, with the special case of
unitmeaning "no fields".Moreover, the actual encoding can be configured with a couple of options:
The options can be passed to
sumWithlike so:I also added some tests which may be worth looking into.