Search code examples
javascriptfunctional-programmingmonadsfunctorapplicative

How to implement a coroutine for applicative computations?


Here is a coroutine that avoids nested patterns like (chain(m) (chain(...)) for monadic computations:

const some = x => none => some => some(x);
const none = none => some => none;

const option = none => some => tx => tx(none) (some);
const id = x => x;

const of = some;
const chain = fm => m => none => some => m(none) (x => fm(x) (none) (some));

const doM = (chain, of) => gf => {
  const it = gf();

  const loop = ({done, value}) =>
    done
      ? of(value)
      : chain(x => loop(it.next(x))) (value);

  return loop(it.next());
};

const tx = some(4),
  ty = some(5),
  tz = none;

const data = doM(chain, of) (function*() {
  const x = yield tx,
    y = yield ty,
    z = yield tz;

  return x + y + z;
});

console.log(
  option(0) (id) (data)); // 0

But I'm not able to implement an equivalent coroutine for applicative computations:

const some = x => none => some => some(x);
const none = none => some => none;

const option = none => some => tx => tx(none) (some);
const id = x => x;

const of = some;
const map = f => t => none => some => t(none) (x => some(f(x)));
const ap = tf => t => none => some => tf(none) (f => t(none) (x => some(f(x))));

const doA = (ap, of) => gf => {
  const it = gf();

  const loop = ({done, value}, initial) =>
    done
      ? value
      : ap(of(x => loop(it.next(x)))) (value);

  return loop(it.next());
};

const tx = some(4),
  ty = some(5),
  tz = none;

const data = doA(ap, of) (function*() {
  const x = yield tx,
    y = yield ty,
    z = yield tz;

  return x + y + z;
});

console.log(
  option(0) (id) (data)); // none => some => ...

This should work, but it doesn't. Where does the additional functorial wrapping come from? I guess I am a bit lost in recursion here.

By the way I know that this only works for deterministic functors/monads.


Solution

  • I'm not able to implement an equivalent coroutine for applicative computations

    Yes, because generator functions are monadic, not just applicative. The operand of a yield expression can depend on the result of the previous yield expression - that's characteristic for a monad.

    Where does the additional functorial wrapping come from? I guess I am a bit lost here.

    You are doing ap(of(…))(…) - this is equivalent to map(…)(…) according to the Applicative laws. Compared to the chain call in the first snippet, this does not do any unwrapping of the result, so you get a nested maybe type (which in your implementation, is encoded as a function).