3

I recently implemented delimited continuations in CPS with reset/shift:

// reset :: ((a -> a) -> a) -> (a -> r) -> r
reset = k => f => f(k(id));

// shift :: ((a -> r) -> (r -> r) -> r) -> (a -> r) -> r
shift = f => k => f(k) (id);

Studying the theory I realized the following connections:

reset ~ function* // scope of the generator function
shift ~ yield

reset ~ async // scope of the asyn function
shift ~ await

As far as I understand the theory, Javascript's generators are a asymmetric, stackless, one-shot and first class coroutines.

  • asymmetric means that the called generator can only yield to its caller
  • stackless means a generator cannot yield from within nested functions
  • one-shot means that a generator can only resume from a specific position once
  • first class means a generator object can be passed around like normal data

Now I want to implement a coroutine based on reset/shift with the following traits:

  • asymmetric
  • stackful
  • multi-shot
  • first class

When looking at the following contrived example

const id = x => x;
const mul = x => y => x * y;
const add = x => y => x + y;
const sub = x => y => x - y;

const reset = k => f => f(k(id));
const shift = f => k => f(k) (id);

const of = x => k => k(x);
const lift2 = f => tx => ty => k => tx(x => ty(y => k(f(x) (y))));

const k0 = lift2(sub)
  (reset
    (lift2(add) (of(3))
      (shift
        (k => of(mul(5) (2))))))
          (of(1)); // 9

const k1 = lift2(sub)
  (reset
    (lift2(add) (of(3))
      (shift
        (k => of(k(mul(5) (2)))))))
          (of(1)); // 12

const k2 = lift2(sub)
  (reset
    (lift2(add) (of(3))
      (shift
        (k => of(k(k(mul(5) (2))))))))
          (of(1)); // 15

console.log(k0(id));
console.log(k1(id));
console.log(k2(id));

it seems that reset/shift already meet the last two criteria, because delimited continuations are just first class, composable functions and I can invoke the continuation k as often as required. To answer the why, I want to bypass the following limitation in connection with the list monad. Are these assumptions correct?

Even if I haven't make any mistakes so far I am overwhelmed by the complexity of the task at this point. I have no clue how to implement the desired coroutine or even where to begin. I didn't found an example implementation either. I don't expect a complete implementation but maybe some guidance to achieve my goal.

Goal

I want to bypass the following limitation of coroutines implemented by Javascript's generators:

const arrMap = f => xs =>
  xs.map(x => f(x));

const arrAp = fs => xs =>
  fs.reduce((acc, f) =>
    acc.concat(xs.map(x => f(x))), []);

const arrChain = xs => fm =>
  xs.reduce((acc, x) => acc.concat(fm(x)), []);

const arrOf = x => [x];

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

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

const z = function*() {
  const x = yield [1,2,3]
  return [x, x];
}

console.log(
  arrChain([1,2,3]) (x => [x, x]));
 
console.log(
  do_(arrOf, arrChain) (z()));
  • You should look at [immutagen](https://github.com/pelotom/immutagen). It can be used to create [multi-shot coroutines](https://stackoverflow.com/a/56815335/783743). However, it does so by replaying the generator over and over again. Hence, it is slow and unsafe for impure computations. – Aadit M Shah Feb 26 '20 at 07:28

0 Answers0