relude-fetch icon indicating copy to clipboard operation
relude-fetch copied to clipboard

Example code without infix operators

Open alavkx opened this issue 5 years ago • 4 comments

I'm struggling to understand the decoding example in the tests

    let decode: Js.Json.t => Belt.Result.t(t, string) =
      json => {
        make
        <$> JD.intFor("userId", json)
        <*> JD.intFor("id", json)
        <*> JD.stringFor("title", json)
        <*> JD.boolFor("completed", json)
        |> Relude.Result.fromValidation
        |> Relude.Result.mapError(_ => "Decode failed");
      };

Would this be equal to the following?

let decode: Js.Json.t => Belt.Result.t(t, string) =
      json => {
        make
        |> map(JD.intFor("userId", json))
        |> ap(JD.intFor("id", json))
        |> ap(JD.stringFor("title", json))
        |> ap(JD.boolFor("completed", json))
        |> Relude.Result.fromValidation
        |> Relude.Result.mapError(_ => "Decode failed");
      };

With some direction, I'd be glad to open a PR adding examples sans infix operators. It would be nice to able to see the two versions side by side for mere mortals like I.

alavkx avatar Feb 03 '20 19:02 alavkx

This is a bit of a weird one, because the "prefix" version of <$> expects the make function to be its first argument, which it will partially apply to the decoded value e.g. map(make, JD.intFor(...)). Then it will pass what's left of the make function as the first argument to <*>, etc. Expanded out, this means things get very nest-y without infix functions:

ap(
  ap(
    map(make, JD.intFor("userId", json)),
    JD.intFor("id", json)
  ),
  JD.stringFor("title", json)
)

However! The special -> operator, of all things, might be useful here because it sends the thing on the left (the make function in this case) into the first slot. I haven't actually tried this, but make->map(...)->ap(...)->ap(...) seems like it should work.

Alternatively, bs-decode's pipeline (inspired by Elm and built around a flipped version of ap), can be used to avoid the infix functions. Or for a small example like this, maybe it makes sense to just use Result.map4. Eventually the new let syntax will get merged and we can avoid all of this. :slightly_smiling_face:

mlms13 avatar Feb 05 '20 19:02 mlms13

Interesting. For some reason I was under the impression that JD decoders is specific to using IO which seemingly isn't the case. I'm using bs-decode in my project currently, so I'll go on using that one. Would you be interested in a PR that adds a bs-decode example to the README (or wherever else you prefer)?

alavkx avatar Feb 05 '20 19:02 alavkx

Just to add to what @mlms13 said, and to provide more background:

The JD stuff is using the Validation type, not IO. Validation is an functor/applicative/monad just like all the others, so it works with the usual map/apply/bind and <$>/<*>/>>= operators the same as everything else (IO, Result, etc.). The bs-decode type follows the same pattern, so this wouldn't behave fundamentally differently for bs-decode in terms of the low-level map/apply/<$>/<*> piping semantics. However bs-decode offers a more streamlined API because it basically flips the order of arguments for apply so that it works better with |>. (Like @mlms13 said above).

https://github.com/mlms13/bs-decode/blob/master/src/DecodeBase.re#L210

<$> is just an alias for let map: ('a => 'b, t('a)) => t('b)

<*> is an alias for let apply: (t('a => 'b), t('a)) => t('b)

so make <$> someApplicativeValue is the same as map(make, someApplicativeValue), so like @mlms13, if you wanted to use piping, you'd need to use -> rather than |> in this example, like make -> map(someApplicativeValue), so the make function is injected as the first argument.

You could use flipped versions of map and apply, and that would work with |>.

Anyway, the point of these tests isn't really to demonstrate how applicative-style decoding works, so maybe it would be best to just take this out of the tests, and just focus on the fetch calls. For usage of bs-decode, it would be better to refer to the docs in bs-decode. That said, I think it's worth the time to learn how <$>/<*> works, because that's a pattern you'll see often in the more powerful FP languages, and it's also the same general pattern as many parsing/decoding libraries.

andywhite37 avatar Feb 05 '20 21:02 andywhite37

Thanks for the clarification guys :) I intend to learn more about infix and figured a side by side example would help. If there's anything I could do to help with documentation, just point it out and I'll open a PR.

alavkx avatar Feb 06 '20 15:02 alavkx