0

See the following code snippet:

const
  fun1 = () => Either.of(1),
  fun2 = () => Either.of(2),
  fun3 = () => Either.of(3),
  fun4 = curry((x, y, z) => Either.of(x + y + z)),
  fun5 = x => Either.of(x + 1),
  fun6 = () => pipeK(
    () => sequence(Either.of, [fun1(), fun2(), fun3()]),
    apply(fun4),
    fun5
  )(),

result = fun6() // returns 7

fun4 requires 3 arguments and I'd like to give them only if all of them are right arguments. That is, sequence will apply each monadic value so I'll get them as a single right containg the raw fun1, fun2, fun3 return values.

Is this the recommended approach?

Click here for run the whole code snippet

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206

1 Answers1

2

No, I would not use sequence with an array and apply. I think the more idiomatic approach is to use ap:

const fun6 = () => chain(fun5, unnest(ap(ap(ap(Either.of(fun4), fun1()), fun2()), fun3())));
// or
const fun6 = () => chain(fun5, unnest(ap(ap(map(fun4, fun1()), fun2()), fun3())));
// or
const fun6 = () => Either.of(fun4).ap(fun1()).ap(fun2()).ap(fun3()).chain(identity).chain(fun5);

The equivalent in Haskell would be fun5 =<< join (fun4 <$> fun1 <*> fun2 <*> fun3). The unnest is needed when fun4 returns an Either, which might not be necessary.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Hey, Bergi. First of all, thank you for your answer. The equivalent in Haskell is afforable, but the JavaScript one is harder to read, isn't it? Would you go with this solution when `sequence` one has by far less noise? – Matías Fidemraizer Oct 23 '17 at 14:29
  • In fact, I can even wrap ramda's `sequence` to use *rest params* and the same call would look less noisy: `sequence(Either.of, fun1(), fun2(), fun3())` – Matías Fidemraizer Oct 23 '17 at 14:31
  • 1
    @MatíasFidemraizer Yes, you can do something like that, but variadic functions don't go well with type systems. Better call it [`liftA3` like Haskell](https://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Applicative.html#v:liftA3) :-) To further improve readability, drop the Unit argument from your "functions" and just make them constant values. – Bergi Oct 23 '17 at 14:54
  • Really it's hard to make a choice. The optimal solution is less readable than a suboptimal one... I understand that using `ap` and `chain` I'm avoiding unnecessary iterations. – Matías Fidemraizer Oct 23 '17 at 15:05
  • Uhm, when you suggested me Haskell's `liftA3` I understand that I can generalize your approach into a `liftA3` the JS/ramda way so I'll be able to call `liftA3(fun5, fun1, fun2, fun3)`, right? – Matías Fidemraizer Oct 23 '17 at 15:10
  • BTW I've tried your code and it has some problem: it doesn't map the result to `fun5`. It gives the `Either.right` without mapping it to `fun5`. – Matías Fidemraizer Oct 23 '17 at 15:32
  • Yes, that `liftA3` is what I meant. – Bergi Oct 23 '17 at 16:10
  • Regarding `fun5`, doesn't `chain` work on eithers? Is the result of `liftA3` the expected one? I think `chain` is better than `pipeK` when there's only a single function to be flat-mapped over a value. – Bergi Oct 23 '17 at 16:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157295/discussion-between-matias-fidemraizer-and-bergi). – Matías Fidemraizer Oct 23 '17 at 16:28
  • There's still an issue. You should use some other identifier for `join` since ramda already has a `join` function (I've already done in my code, but it's just for the sake of correctness of your answer). – Matías Fidemraizer Oct 23 '17 at 16:44
  • Also, what about providing the dotted version too? `liftA3 = (fun4, fun1, fun2, fun3) => Either.of(fun4).ap(fun1()).ap(fun2()).ap(fun3()).chain(identity)` and `fun6 = () => liftA3(fun4, fun1, fun2, fun3).chain(fun5)` – Matías Fidemraizer Oct 23 '17 at 16:49
  • Ah, they called it `unnest`. – Bergi Oct 23 '17 at 17:18
  • 1
    Thanks again for your effort. In fact, both first and second approaches are definitively the way to go as they can work with any applicative, while the third one is tied to `Either`. – Matías Fidemraizer Oct 24 '17 at 08:20
  • Uhm I can't edit my previous comment. But I wanted to say that #2 approach is the best because it can handle any applicative. – Matías Fidemraizer Oct 24 '17 at 08:29
  • ...any applicative and functor ;D – Matías Fidemraizer Oct 24 '17 at 08:30