Compose method in recompose Library - javascript

I was looking at the compose function in recompose library by #acdlite to compose boundary conditions for Higher Order Components and this is what it looks the compose function looks like
const compose = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
However, I tried Eric-Elliott's one liner approach to compose, from https://medium.com/javascript-scene/reduce-composing-software-fe22f0c39a1d, specifically, this piece of code.
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
I tried using both these variants, in my react component like so,
const ListWithConditionalRendering = compose(
withLoadingIndicator,
withDataNull,
withListEmpty
)(Users);
and they both seem to work fine. I am unable to understand if there is any difference in the way the above functions work, if so, what are they.

There's a few differences for very niche scenarios that might be helpful to be aware of.
The first one precomposes a function, which means it calls reduce() when it is composed rather than when it will be called. In contrast, the second approach returns a scoped function that calls reduceRight() when it is called, rather than when it was composed.
The first method accepts multiple arguments to the last function in the array, while the second method only accepts one argument:
const compose1 = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
const compose2 = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const f = s => (...args) => (console.log('function', s, 'length', args.length), args);
compose1(f(1), f(2), f(3))(1, 2, 3);
compose2(f(4), f(5), f(6))(1, 2, 3);
The first method may result in a stack overflow if the array of functions is very large because it is pre-composed, whereas the second method is (relatively)† stack safe:
const compose1 = (...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
const compose2 = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const f = v => v;
try {
compose1.apply(null, Array.from({ length: 1e5 }, () => f))();
console.log('1 is safe');
} catch (e) {
console.log('1 failed');
}
try {
compose2.apply(null, Array.from({ length: 1e5 }, () => f))();
console.log('2 is safe');
} catch (e) {
console.log('2 failed');
}
† The second method will still result in a stack overflow if ...fns is too large because arguments are also allocated on the stack.

If you are interested in what structure the reduce-composition actually builds, you can visualize it as follows:
/* original:
const compose = (...funcs) =>
funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
*/
const compose = (...funcs) =>
funcs.reduce((a, b) => `((...args) => ${a}(${b}(...args)))`, $_("id"));
const $_ = name =>
`${name}`;
const id = x => x;
const inc = x => x + 1;
const sqr = x => x * x;
const neg = x => -x;
const computation = compose($_("inc"), $_("sqr"), $_("neg"));
console.log(computation);
/* yields:
((...args) => ((...args) => ((...args) =>
id(inc(...args))) (sqr(...args))) (neg(...args)))
*/
console.log(eval(computation) (2)); // 5 (= id(inc(sqr(neg(2))))
So what is going on here? I replaced the inner function (...args) => a(b(...args)) with a Template-String and arg => arg with the $_ helper function. Then I wrapped the Template-String in parenthesis, so that the resulting String represents an IIFE. Last but not least I pass $_ helper functions with proper names to compose.
$_ is a bit odd but it is really helpful to visualize unapplied/partially applied functions.
You can see from the computational structure that the reduce-composition builds a nested structure of anonymous functions and rest/spread operations are scattered throughout the code.
Visualizing and interpreting partially applied functions is hard. We can simplify it by omitting the inner anonymous function:
const compose = (...funcs) =>
funcs.reduce($xy("reducer"), $_("id"));
const $_ = name =>
`${name}`;
const $xy = name => (x, y) =>
`${name}(${x}, ${y})`;
const id = x => x;
const inc = x => x + 1;
const sqr = x => x * x;
const neg = x => -x;
console.log(
compose($_("inc"), $_("sqr"), $_("neg"))
// reducer(reducer(reducer(id, inc), sqr), neg)
);
We can further simplify by actually running the composition:
const compose = (...funcs) =>
funcs.reduce((a, b) => (...args) => a(b(...args)), $x("id"));
const $x = name => x =>
`${name}(${x})`;
console.log(
compose($x("inc"), $x("sqr"), $x("neg")) (2) // id(inc(sqr(neg(2))))
);
I believe that the visualization of complex computations like this is a powerful technique to comprehend them correctly and to gain a better understanding of nested/recursive computational structures.

Implementation show and tell? Okay -
const identity = x =>
x
const compose = (f = identity, ...fs) => x =>
f === identity
? x
: compose (...fs) (f (x))
const add1 = x =>
x + 1
console .log
( compose () (0) // 0
, compose (add1) (0) // 1
, compose (add1, add1) (0) // 2
, compose (add1, add1, add1) (0) // 3
)
Or instead of using compose in-line ...
const ListWithConditionalRendering = compose(
withLoadingIndicator,
withDataNull,
withListEmpty
)(Users);
You could make a sort of "forward composition" function where the argument comes first -
const $ = x => k =>
$ (k (x))
const add1 = x =>
x + 1
const double = x =>
x * 2
$ (0) (add1) (console.log)
// 1
$ (2) (double) (double) (double) (console.log)
// 16
$ (2) (double) (add1) (double) (console.log)
// 10
$ is useful when you can maintain a pattern of -
$ (value) (pureFunc) (pureFunc) (pureFunc) (...) (effect)
Above, $ puts a value into a sort of "pipeline", but there's no way to take the value out. A small adjustment allows us write very flexible variadic expressions. Below, we use $ as a way of delimiting the beginning and ending of a pipeline expression.
const $ = x => k =>
k === $
? x
: $ (k (x))
const double = x =>
x * 2
const a =
$ (2) (double) ($)
const b =
$ (3) (double) (double) (double) ($)
console .log (a, b)
// 4 24
This variadic interface gives you the ability to write expressions similar to the coveted |> operator found in other more function-oriented languages -
value
|> pureFunc
|> pureFunc
|> ...
|> pureFunc
5 |> add1
|> double
|> double
// 24
Using $, that translates to -
$ (value) (pureFunc) (pureFunc) (...) (pureFunc) ($)
$ (5) (add1) (double) (double) ($) // 24
The technique also mixes nicely with curried functions -
const $ = x => k =>
$ (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
$ (1) (add (2)) (mult (3)) (console.log)
// 9
Or with a slightly more interesting example -
const $ = x => k =>
$ (k (x))
const flatMap = f => xs =>
xs .flatMap (f)
const join = y => xs =>
xs .join (y)
const twice = x =>
[ x, x ]
$ ('mississippi')
(([...chars]) => chars)
(flatMap (twice))
(join (''))
(console.log)
// 'mmiissssiissssiippppii'

Related

Please why is my code not working,do I need to install any library

Recently started functional programming and all explanations of the pipe and compose using reduce which I have seen are very sketchy.
const x = 4
const add2 = x + 2
const multiplyBy5 = x * 5
const subtract1 = x - 1
pipe = (...functions) =>
(x) => functions.reduce((v, function) => function(v), x)
const result = pipe(add2, multiplyBy5, subtract1)(4)
console.log(result)
There were 2 errors.
The first one was that the x, add2, multiplyBy5 and subtract1 were not functions, but mere definitions.
The other was that you naming a variable (using the arguments) to a name that is a "reserved" word such as "function" did break the syntax parser.
const x = (x) => x
const add2 = (x) => x+2
const multiplyBy5 = (x) => x*5
const subtract1 = (x) => x-1
const pipe = (...functions) => (x) => functions.reduce((v,fn)=>fn(v),x)
const result = pipe(
add2,
multiplyBy5,
subtract1,
)(4);
console.log(result)
I think it should be done like this:
const add2 = (x) => x+2
const multiplyBy5 = (x) => x*5
const subtract1 = (x) => x-1
const pipe=(...functions) => (x) => functions.reduce((v, functionA) => functionA(v), x)
const result=pipe(add2, multiplyBy5, subtract1)(4)
console.log(result)

Similar curry functions producing different results

I am learning functional javascript and I came across two different implementations of the curry function. I am trying to understand the difference between the two they seem similar yet one works incorrectly for some cases and correctly for other cases.
I have tried interchanging the functions the one defined using es6 'const'
works for simple cases but when using 'filter' to filter strings the results are incorrect, but with integers it produces the desired results.
//es6
//Does not work well with filter when filtering strings
//but works correctly with numbers
const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length === fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
);
//Regular js
//Works well for all cases
function curry(fn) {
const arity = fn.length;
return function $curry(...args) {
if (args.length < arity) {
return $curry.bind(null, ...args);
}
return fn.call(null, ...args);
};
}
const match = curry((pattern, s) => s.match(pattern));
const filter = curry((f, xs) => xs.filter(f));
const hasQs = match(/q/i);
const filterWithQs = filter(hasQs);
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]));
//Output:
//es6:
[ 'hello', 'quick', 'sand', 'qwerty', 'quack' ]
//regular:
[ 'quick', 'qwerty', 'quack' ]
If you change filter to use xs.filter(x => f(x)) instead of xs.filter(f) it will work -
const filter = curry((f, xs) => xs.filter(x => f(x)))
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
The reason for this is because Array.prototype.filter passes three (3) arguments to the "callback" function,
callback - Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:
element - The current element being processed in the array.
index (Optional) - The index of the current element being processed in the array.
array (Optional) - The array filter was called upon.
The f you are using in filter is match(/q/i), and so when it is called by Array.prototype.filter, you are getting three (3) extra arguments instead of the expected one (1). In the context of curry, that means a.length will be four (4), and since 4 === fn.length is false (where fn.length is 2), the returned value is curry(fn, a), which is another function. Since all functions are considered truthy values in JavaScript, the filter call returns all of the input strings.
// your original code:
xs.filter(f)
// is equivalent to:
xs.filter((elem, index, arr) => f(elem, index, arr))
By changing filter to use ...filter(x => f(x)), we only allow one (1) argument to be passed to the callback, and so curry will evaluate 2 === 2, which is true, and the return value is the result of evaluating match, which returns the expected true or false.
// the updated code:
xs.filter(x => f(x))
// is equivalent to:
xs.filter((elem, index, arr) => f(elem))
An alternative, and probably better option, is to change the === to >= in your "es6" curry -
const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length >= fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
)
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
This allows you to "overflow" function parameters "normally", which JavaScript has no problem with -
const foo = (a, b, c) => // has only three (3) parameters
console.log(a + b + c)
foo(1,2,3,4,5) // called with five (5) args
// still works
// => 6
Lastly here's some other ways I've written curry over the past. I've tested that each of them produce the correct output for your problem -
by auxiliary loop -
const curry = f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (f.length, [])
}
versatile curryN, works with variadic functions -
const curryN = n => f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (n, [])
};
// curry derived from curryN
const curry = f => curryN (f.length) (f)
spreads for days -
const curry = (f, ...xs) => (...ys) =>
f.length > xs.length + ys.length
? curry (f, ...xs, ...ys)
: f (...xs, ...ys)
homage to the lambda calculus and Howard Curry's fixed-point Y-combinator -
const U =
f => f (f)
const Y =
U (h => f => f (x => U (h) (f) (x)))
const curryN =
Y (h => xs => n => f =>
n === 0
? f (...xs)
: x => h ([...xs, x]) (n - 1) (f)
) ([])
const curry = f =>
curryN (f.length) (f)
and my personal favourites -
// for binary (2-arity) functions
const curry2 = f => x => y => f (x, y)
// for ternary (3-arity) functions
const curry3 = f => x => y => z => f (x, y, z)
// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f (...xs, ...ys)
Lastly a fun twist on #Donat's answer that enables anonymous recursion -
const U =
f => f (f)
const curry = fn =>
U (r => (...args) =>
args.length < fn.length
? U (r) .bind (null, ...args)
: fn (...args)
)
The main difference here is not the es6 syntax but how the arguments are partially applied to the function.
First version: curry(fn, a)
Second versison: $curry.bind(null, ...args)
It works for only one step of currying (as needed in your example) if you change first version (es6) to fn.bind(null, ...args)
The representation of the "Regular js" version in es6 syntax would look like this (you need the constant to have a name for the function in the recursive call):
curry = (fn) => {
const c = (...args) => (
args.length < fn.length ? c.bind(null, ...args) : fn(...args)
);
return c;
}

How to share intermediate results of continuations?

Please note that even though the example in this question is encoded in Javascript, the underlying concepts are common in Haskell and I while I prefer to express myself in Javascript I also appreciate answers in Haskell.
In Javascript I use CPS to handle asynchronous computations according to monadic principles. For the sake of simplicity, however, I will use the normal continuation monad for this question.
As soon as my continuation compositions grow, I keep finding myself in a situation where I need access to intermediate results of these compositions. Since Javascript is imperative it is easy to store such results in variables and access them later. But since we're talking about continuations accessing intermediate results means calling functions and accessing them several times means a lot of re-evaluation.
This seems to be well suited for memoization. But how can I memoize a function's return value if that very function doesn't return anything but merely calls its continuation (and btw. as I mentioned before I use asynchronous functions that also don't return anything in the current cycle of Javascript's event loop).
It seems as if I have to extract the right continuation. Maybe this is possible with delimited continuations through shift/reset, but I don't know how to apply these combinators. This issue is probably not that hard to solve and I'm just confused by the magical land of continuation passing style...so please be indulgent with me.
Here is a simplified example of Cont without memoization in Javascript:
const taggedLog = tag => s =>
(console.log(tag, s), s);
const id = x => x;
const Cont = k => ({
runCont: k,
[Symbol.toStringTag]: "Cont"
});
const contAp = tf => tx =>
Cont(k => tf.runCont(f => tx.runCont(x => k(f(x)))));
const contLiftA2 = f => tx => ty =>
contAp(contMap(f) (tx)) (ty);
const contOf = x => Cont(k => k(x));
const contMap = f => tx =>
Cont(k => tx.runCont(x => k(f(x))));
const contReset = tx => // delimited continuations
contOf(tx.runCont(id));
const contShift = f => // delimited continuations
Cont(k => f(k).runCont(id));
const inc = contMap(x => taggedLog("eval inc") (x + 1));
const inc2 = inc(contOf(2));
const inc3 = inc(contOf(3));
const add = contLiftA2(x => y => taggedLog("eval add") (x + y));
const mul = contLiftA2(x => y => taggedLog("eval mul") (x * y));
const intermediateResult = add(inc2) (inc3);
mul(intermediateResult) (intermediateResult).runCont(id);
/*
should only log four lines:
eval inc 3
eval inc 4
eval add 7
eval mul 49
*/
Your problems seems to be that your Cont has no monad implementation yet. With that, it's totally simple to access previous results - they're just in scope (as constants) of nested continuation callbacks:
const contChain = tx => f =>
Cont(k => tx.runCont(r => f(r).runCont(k)));
contChain( add(inc2) (inc3), intermediateResult => {
const intermediateCont = contOf(intermediateResult);
return mul(intermediateCont) (intermediateCont);
}).runCont(id);
(Of course it's a little weird that all your functions are already lifted and take Cont values as arguments - they shouldn't do that and simply be functions that return Cont values)
Your code in Haskell:
import Control.Monad.Cont
import Control.Applicative
let inc = liftA (+1)
let inc2 = inc $ return 2
let inc3 = inc $ return 3
let add = liftA2 (+)
let mul = liftA2 (*)
(`runCont` id) $ add inc2 inc3 >>= \intermediateResult ->
let intermediateCont = return intermediateResult
in mul intermediateCont intermediateCont
-- 49
{- or with do notation: -}
(`runCont` id) $ do
intermediateResult <- add inc2 inc3
let intermediateCont = return intermediateResult
mul intermediateCont intermediateCont
-- 49
(I haven't used monad transformers to make a taggedLog side effect)
It seems that I can't avoid getting impure to obtain the desired behavior. The impurity is only local though, because I just replace the continuation chain with its result value. I can do this without changing the behavior of my program, because this is exactly what referential transparency guarantees us.
Here is the transformation of the Cont constructor:
const Cont = k => ({
runCont: k,
[Symbol.toStringTag]: "Cont"
});
// becomes
const Cont = k => thisify(o => { // A
o.runCont = (res, rej) => k(x => { // B
o.runCont = l => l(x); // C
return res(x); // D
}, rej); // E
o[Symbol.toStringTag] = "Cont";
return o;
});
thisify in line A merely mimics this context, so that the Object to be constructed is aware of itself.
Line B is the decisive change: Instead of just passing res to the continuation k I construct another lambda that stores the result x wrapped in a continuation under the runTask property of the current Task object (C), before it calls res with x (D).
In case of an error rej is just applied to x, as usual (E).
Here is the runnning example from above, now working as expected:
const taggedLog = pre => s =>
(console.log(pre, s), s);
const id = x => x;
const thisify = f => f({}); // mimics this context
const Cont = k => thisify(o => {
o.runCont = (res, rej) => k(x => {
o.runCont = l => l(x);
return res(x);
}, rej);
o[Symbol.toStringTag] = "Cont";
return o;
});
const contAp = tf => tx =>
Cont(k => tf.runCont(f => tx.runCont(x => k(f(x)))));
const contLiftA2 = f => tx => ty =>
contAp(contMap(f) (tx)) (ty);
const contOf = x => Cont(k => k(x));
const contMap = f => tx =>
Cont(k => tx.runCont(x => k(f(x))));
const inc = contMap(x => taggedLog("eval inc") (x + 1));
const inc2 = inc(contOf(2));
const inc3 = inc(contOf(3));
const add = contLiftA2(x => y => taggedLog("eval add") (x + y));
const mul = contLiftA2(x => y => taggedLog("eval mul") (x * y));
const intermediateResult = add(inc2) (inc3);
mul(intermediateResult) (intermediateResult).runCont(id);
/* should merely log
eval inc 3
eval inc 4
eval add 7
eval add 49
*/

How to implement a coroutine based on multi-shot delimited continuations?

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()));

How to implement a stack-safe chainRec operator for the continuation monad?

I am currently experimenting with the continuation monad. Cont is actually useful in Javascript, because it abstracts from the callback pattern.
When we deal with monadic recursion, there is always the risk of a stack overflow, because the recursive call isn't in tail position:
const chain = g => f => k =>
g(x => f(x) (k));
const of = x => k =>
k(x);
const id = x =>
x;
const inc = x =>
x + 1;
const repeat = n => f => x =>
n === 0
? of(x)
: chain(of(f(x))) (repeat(n - 1) (f));
console.log(
repeat(1e6) (inc) (0) (id) // stack overflow
);
However, even if we are able to transform some cases into tail recursion we are still doomed, because Javascript doesn't have TCO. Consequently we have to fall back to a loop at some point.
puresrcipt has a MonadRec typeclass with a tailRecM operator that enables tail recursive monadic computations for some monads. So I tried to implement chainRec in Javascript mainly according to the fantasy land spec:
const chain = g => f => k => g(x => f(x) (k));
const of = x => k => k(x);
const id = x => x;
const Loop = x =>
({value: x, done: false});
const Done = x =>
({value: x, done: true});
const chainRec = f => x => {
let step = f(Loop, Done, x);
while (!step.done) {
step = f(Loop, Done, step.value);
}
return of(step.value);
};
const repeat_ = n => f => x =>
chainRec((Loop, Done, [n, x]) => n === 0 ? Done(x) : Loop([n - 1, f(x)])) ([n, x]);
console.log(
repeat_(1e6) (n => n + 1) (0) (id) // 1000000
);
This works, but it looks a lot like cheating, because it seems to bypass the monadic chaining and thus Cont's context. In this case the context is just "the rest of the computation", ie. function composition in reverse and as a result the expected value is returned. But does it work for any monad?
To make it clear what I mean take a look at the following code snippet from this outstanding answer:
const Bounce = (f,x) => ({ isBounce: true, f, x })
const Cont = f => ({
_runCont: f,
chain: g =>
Cont(k =>
Bounce(f, x =>
Bounce(g(x)._runCont, k)))
})
// ...
const repeat = n => f => x => {
const aux = (n,x) =>
n === 0 ? Cont.of(x) : Cont.of(f(x)).chain(x => aux(n - 1, x))
return runCont(aux(n,x), x => x)
}
Here chain is somehow incorporated into the recursive algorithm, that is the monadic effect can occur. Unfortunately, I cannot decipher this operator or reconcile it with the stack-unsafe version (Bounce(g(x)._runCont, k) seems to be the f(x) (k) portion, though).
Ultimately, my question is if I messed up the implementation of chainRecor misunderstood the FL spec or both or none of it?
[EDIT]
Both given answers are very helpful by looking at the problem from different perspectives and deserve to be accepted. Since I can only accept one - hey stackoverflow, the world isn't that simple!!! - I won't accept any.
with best wishes,
I think this might be what you're looking for,
const chainRec = f => x =>
f ( chainRec (f)
, of
, x
)
Implementing repeat is just as you have it – with two exceptions (thanks #Bergi for catching this detail). 1, loop and done are the chaining functions, and so the chainRec callback must return a continuation. And 2, we must tag a function with run so cont knows when we can safely collapse the stack of pending continuations – changes in bold
const repeat_ = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done) // cont chain done
: of ([ n - 1, f (x) ]) (loop) // cont chain loop
([ n, x ])
const repeat = n => f => x =>
repeat_ (n) (f) (x) (run (identity))
But, if you're using chainRec as we have here, of course there's no reason to define the intermediate repeat_. We can define repeat directly
const repeat = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done)
: of ([ n - 1, f (x) ]) (loop)
([ n, x ])
(run (identity))
Now for it to work, you just need a stack-safe continuation monad – cont (f) constructs a continuation, waiting for action g. If g is tagged with run, then it's time to bounce on the trampoline. Otherwise constructor a new continuation that adds a sequential call for f and g
// not actually stack-safe; we fix this below
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x), k)))
const of = x =>
cont (k => k (x))
Before we go further, we'll verify things are working
const TAG =
Symbol ()
const tag = (t, x) =>
Object.assign (x, { [TAG]: t })
const is = (t, x) =>
x && x [TAG] === t
// ----------------------------------------
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x), k)))
const of = x =>
cont (k => k (x))
const chainRec = f => x =>
f ( chainRec (f)
, of
, x
)
const run = x =>
tag (run, x)
const call = (f, x) =>
tag (call, { f, x })
const trampoline = t =>
{
let acc = t
while (is (call, acc))
acc = acc.f (acc.x)
return acc
}
// ----------------------------------------
const identity = x =>
x
const inc = x =>
x + 1
const repeat = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done)
: of ([ n - 1, f (x) ]) (loop))
([ n, x ])
(run (identity))
console.log (repeat (1e3) (inc) (0))
// 1000
console.log (repeat (1e6) (inc) (0))
// Error: Uncaught RangeError: Maximum call stack size exceeded
where's the bug?
The two implementations provided contain a critical difference. Specifically, it's the g(x)._runCont bit that flattens the structure. This task is trivial using the JS Object encoding of Cont as we can flatten by simply reading the ._runCont property of g(x)
const Cont = f =>
({ _runCont: f
, chain: g =>
Cont (k =>
Bounce (f, x =>
// g(x) returns a Cont, flatten it
Bounce (g(x)._runCont, k)))
})
In our new encoding, we're using a function to represent cont, and unless we provide another special signal (like we did with run), there's no way to access f outside of cont once it's been partially applied – look at g (x) below
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
// g (x) returns partially-applied `cont`, how to flatten?
call (g (x), k)))
Above, g (x) will return a partially-applied cont, (ie cont (something)), but this means that the entire cont function can nest infinitely. Instead of cont-wrapped something, we only want something.
At least 50% of the time I spent on this answer has been coming up with various ways to flatten partially-applied cont. This solution isn't particularly graceful, but it does get the job done and highlights precisely what needs to happen. I'm really curious to see what other encodings you might find – changes in bold
const FLATTEN =
Symbol ()
const cont = f => g =>
g === FLATTEN
? f
: is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x) (FLATTEN), k)))
all systems online, captain
With the cont flattening patch in place, everything else works. Now see chainRec do a million iterations…
const TAG =
Symbol ()
const tag = (t, x) =>
Object.assign (x, { [TAG]: t })
const is = (t, x) =>
x && x [TAG] === t
// ----------------------------------------
const FLATTEN =
Symbol ()
const cont = f => g =>
g === FLATTEN
? f
: is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x) (FLATTEN), k)))
const of = x =>
cont (k => k (x))
const chainRec = f => x =>
f ( chainRec (f)
, of
, x
)
const run = x =>
tag (run, x)
const call = (f, x) =>
tag (call, { f, x })
const trampoline = t =>
{
let acc = t
while (is (call, acc))
acc = acc.f (acc.x)
return acc
}
// ----------------------------------------
const identity = x =>
x
const inc = x =>
x + 1
const repeat = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done)
: of ([ n - 1, f (x) ]) (loop))
([ n, x ])
(run (identity))
console.log (repeat (1e6) (inc) (0))
// 1000000
evolution of cont
When we introduced cont in the code above, it's not immediately obvious how such an encoding was derived. I hope to shed some light on that. We start with how we wish we could define cont
const cont = f => g =>
cont (comp (g,f))
const comp = (f, g) =>
x => f (g (x))
In this form, cont will endlessly defer evaluation. The only available thing we can do is apply g which always creates another cont and defers our action. We add an escape hatch, run, which signals to cont that we don't want to defer any longer.
const cont = f => g =>
is (run, g)
? f (g)
: cont (comp (g,f))
const is = ...
const run = ...
const square = x =>
of (x * x)
of (4) (square) (square) (run (console.log))
// 256
square (4) (square) (run (console.log))
// 256
Above, we can begin to see how cont can express beautiful and pure programs. However in an environment without tail-call elimination, this still allows programs to build deferred functions sequences that exceed the evaluator's stack limit. comp directly chains functions, so that's out of the picture. Instead we'll sequence the functions using a call mechanism of our own making. When the program signals run, we collapse the stack of calls using trampoline.
Below, we arrive at the form we had before the flatten fix was applied
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (comp (g,f))
: cont (k =>
call (f, x =>
call (g (x), k)))
const trampoline = ...
const call = ...
wishful thinking
Another technique we were using above is one of my favorites. When I write is (run, g), I don't know how I'm going to represent is or run right away, but I can figure it out later. I use the same wishful thinking for trampoline and call.
I point this out because it means I can keep all of that complexity out of cont and just focus on its elementary structure. I ended up with a set of functions that gave me this "tagging" behavior
// tag contract
// is (t, tag (t, value)) == true
const TAG =
Symbol ()
const tag = (t, x) =>
Object.assign (x, { [TAG]: t })
const is = (t, x) =>
x && x [TAG] === t
const run = x =>
tag (run, x)
const call = (f, x) =>
tag (call, { f, x })
Wishful thinking is all about writing the program you want and making your wishes come true. Once you fulfill all of your wishes, your program just magically works!
Did I mess up the implementation of chainRec, or misunderstood the FantasyLand spec, or both or none of it?
Probably both, or at least the first. Notice that the type should be
chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m b
wherein m is Cont and c is your Done/Loop wrapper over a or b:
chainRec :: ((a -> DL a b, b -> DL a b, a) -> Cont (DL a b), a) -> Cont b
But your chainRec and repeat implementations don't use continations at all!
If we implement just that type, without the requirement that it should need constant stack space, it would look like
const chainRec = f => x => k =>
f(Loop, Done, x)(step =>
step.done
? k(step.value) // of(step.value)(k)
: chainRec(f)(step.value)(k)
);
or if we drop even the lazyness requirement (similar to transforming chain from g => f => k => g(x => f(x)(k)) to just g => f => g(f) (i.e. g => f => k => g(x => f(x))(k))), it would look like
const chainRec = f => x =>
f(Loop, Done, x)(step =>
step.done
? of(step.value)
: chainRec(f)(step.value)
);
or even dropping Done/Loop
const join = chain(id);
const chainRec = f => x => join(f(chainRec(f), of, x));
(I hope I'm not going out on a limb too far with that, but it perfectly presents the idea behind ChainRec)
With the lazy continuation and the non-recursive trampoline, we would however write
const chainRec = f => x => k => {
let step = Loop(x);
do {
step = f(Loop, Done, step.value)(id);
// ^^^^ unwrap Cont
} while (!step.done)
return k(step.value); // of(step.value)(k)
};
The loop syntax (initialise step with an f call, do/while instead of do) doesn't really matter, yours is fine as well but the important part is that f(Loop, Done, v) returns a continuation.
I'll leave the implementation of repeat as an exercise to the reader :D
(Hint: it might become more useful and also easier to get right if you have the repeated function f already use continuations)

Categories