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

Example code without infix operators #7

Open
alavkx opened this issue Feb 3, 2020 · 4 comments
Open

Example code without infix operators #7

alavkx opened this issue Feb 3, 2020 · 4 comments

Comments

@alavkx
Copy link

alavkx commented Feb 3, 2020

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.

@mlms13
Copy link
Member

mlms13 commented Feb 5, 2020

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. 🙂

@alavkx
Copy link
Author

alavkx commented Feb 5, 2020

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)?

@andywhite37
Copy link
Member

andywhite37 commented Feb 5, 2020

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.

@alavkx
Copy link
Author

alavkx commented Feb 6, 2020

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.

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