json-extra
json-extra copied to clipboard
andThen2 : (a -> b -> Decoder c) -> Decoder a -> Decoder b -> Decoder c
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.
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?
map2 Tuple.pair is how I would implement it. Would we want andThen3 and so on?