dillonkearns / elm-ts-json

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use opaque type for andThen, allow succeed and fail

dillonkearns opened this issue · comments

@neurodynamic thanks again for the feedback on the API.

To follow up on the Discourse discussion, I think it would make sense to use an opaque type like the UnionEncodeValue type.

The benefit is that the library then guarantees that all possible decoders that may continue on from the andThen have been "registered". Otherwise, you could use any decoder within the andThen as it is in its current state, and that will cause the decoder to have inaccurate type information.

Here's an example with the current API:

                field "version" int
                    |> andThen
                        (andThenInit
                            (\version ->
                                case version of
                                    1 ->
                                        field "firstName" string

                                    _ ->
                                        at [ "name", "first" ] string
                            )
                        )
                    |> expectDecodes
                        { input = """{"version": 2, "name": {"first": "Jane"}}"""
                        , output = "Jane"
                        , typeDef = "{ version : number }"

Note that the type def doesn't have any information about the firstName or name.first fields. This is inaccurate.

If you "register" the continuation decoders, as is intended, then you get nice, accurate TypeScript type information:

field "version" int
    |> andThen
        (andThenInit
            (\v1Decoder v2Decoder version ->
                case version of
                    1 ->
                        v1Decoder

                    _ ->
                        v2Decoder
            )
            |> andThenDecoder (field "firstName" string)
            |> andThenDecoder (at [ "name", "first" ] string)
        )
    |> expectDecodes
        { input = """{"version": 2, "name": {"first": "Jane"}}"""
        , output = "Jane"
        , typeDef = "({ version : number } & { name : { first : string } } | { firstName : string })"
        }

I think this is quite nice! The improvement here would be for the API to enforce using the API in this intended way.

Guaranteeing correct usage with opaque types

That's where the opaque type comes in. Right now, the continuation takes a TsJson.Decode.Decoder. If we require it to use a simple wrapper type around that type (let's call it AndThenDecoder), then we can guarantee that the only way to get that wrapped type is by passing it through andThenDecoder ....

type AndThenDecoder a = AndThenDecoder (Decoder a)

succeed and fail

I think these two types may be special cases. You can't "register" either of these with andThenDecoder, because that would force you to have the success value or failure message when you register it. But that defeats the purpose of andThen.

I think adding these functions to the API will solve that problem:

andThenSucceed : a -> AndThenDecoder a
andThenSucceed a =
    AndThenDecoder (succeed a)

andThenFail : String -> AndThenDecoder a
andThenFail errorMessage =
    AndThenDecoder (fail errorMessage)

As far as the resulting TypeScript type defs from a Decoder, using succeed or fail within andThen doesn't change that. So I think this design will work nicely!