Skip to content
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

andThen2 : (a -> b -> Decoder c) -> Decoder a -> Decoder b -> Decoder c #25

Open
skyqrose opened this issue Aug 3, 2019 · 2 comments
Open
Assignees

Comments

@skyqrose
Copy link

skyqrose commented Aug 3, 2019

For combining multiple decode results in a way that might fail.

E.g if you have json that looks like

{
  "ids": [1, 2, 3, 4],
  "names": ["Not", "Enough", "Names"]
}

and you want to zip the two lists together, but fail if they're different lengths, you could do

Decode.Extra.andThen2
    (\ids names ->
        if List.length ids == List.length names
            Decode.succeed (List.zip ids names)
        else
            Decode.fail "expected the same number of ids and names"
    )
    (field "ids" (list int))
    (field "names" (list string))

Can be implemented as something like

andThen2 : (a -> b -> Decoder c) -> Decoder a -> Decoder b -> Decoder c
andThen2 f decoderA decoderB =
    map2 Tuple.pair decoderA decoderB
        |> andThen (\(a, b) -> f a b)

It's simple enough to use the tuple everywhere, but it reads a little better to be able to say andThen2 instead.

@zwilias zwilias self-assigned this Aug 5, 2019
@zwilias
Copy link
Member

zwilias commented Aug 13, 2019

An alternative implementation (and also a way to work around the lack of this) is with something like map2 f decoderA decoderB |> andThen identity, where andThen identity is a way to implement join : Decoder (Decoder a) -> Decoder a.

In my personal projects, I prefer using a pipeline-style of decoding, so I'm likely to end up writing this like so:

decoder : Decoder (List ( Int, String ))
decoder =
    Decode.succeed zip
        |> Decode.andMap (Decode.field "ids" (Decode.list Decode.int))
        |> Decode.andMap (Decode.field "names" (Decode.list Decode.string))
        |> Decode.andThen identity


-- Couldn't help myself and had to turn this into a tail recursive thing
zip : List a -> List b -> Decoder (List ( a, b ))
zip left right =
	zipHelper left right []


zipHelper : List a -> List b -> List ( a, b ) -> Decoder (List ( a, b ))
zipHelper left right acc =
	case ( left, right ) of
		( [], [] ) ->
			Decode.succeed (List.reverse acc)

		( x :: xs, y :: ys ) ->
			zipHelper xs ys (( x, y ) :: acc)

		( _, _ ) ->
			Decode.fail "Expected lists of equal length"

So, I'm torn. On the one hand, it takes some insight to realize map2 + join is andThen2 (in the same way that map + join = andThen), whereas andThenX is fairly obvious. On the other hand, adding a join at the end makes this easily usable with all mapX and andMap sort of deals, without needing a whole bunch of functions to cover the spectrum. Finally, there is precedent for having a join function for pipeline style decoding 🤔

Long story short, I'm curious how knowing about the andThen identity trick might change your thoughts on this?

@mgold
Copy link
Member

mgold commented Aug 15, 2019

map2 Tuple.pair is how I would implement it. Would we want andThen3 and so on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants