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.
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).
Related
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)
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
*/
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()));
I'm following an article about Transducers in JavaScript, and in particular I have defined the following functions
const reducer = (acc, val) => acc.concat([val]);
const reduceWith = (reducer, seed, iterable) => {
let accumulation = seed;
for (const value of iterable) {
accumulation = reducer(accumulation, value);
}
return accumulation;
}
const map =
fn =>
reducer =>
(acc, val) => reducer(acc, fn(val));
const sumOf = (acc, val) => acc + val;
const power =
(base, exponent) => Math.pow(base, exponent);
const squares = map(x => power(x, 2));
const one2ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
res1 = reduceWith(squares(sumOf), 0, one2ten);
const divtwo = map(x => x / 2);
Now I want to define a composition operator
const more = (f, g) => (...args) => f(g(...args));
and I see that it is working in the following cases
res2 = reduceWith(more(squares,divtwo)(sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares)(sumOf), 0, one2ten);
which are equivalent to
res2 = reduceWith(squares(divtwo(sumOf)), 0, one2ten);
res3 = reduceWith(divtwo(squares(sumOf)), 0, one2ten);
The whole script is online.
I don't understand why I can't concatenate also the last function (sumOf) with the composition operator (more). Ideally I'd like to write
res2 = reduceWith(more(squares,divtwo,sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares,sumOf), 0, one2ten);
but it doesn't work.
Edit
It is clear that my initial attempt was wrong, but even if I define the composition as
const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);
I still can't replace compose(divtwo,squares)(sumOf) with compose(divtwo,squares,sumOf)
Finally I've found a way to implement the composition that seems to work fine
const more = (f, ...g) => {
if (g.length === 0) return f;
if (g.length === 1) return f(g[0]);
return f(more(...g));
}
Better solution
Here it is another solution with a reducer and no recursion
const compose = (...fns) => (...x) => fns.reduceRight((v, fn) => fn(v), ...x);
const more = (...args) => compose(...args)();
usage:
res2 = reduceWith(more(squares,divtwo,sumOf), 0, one2ten);
res3 = reduceWith(more(divtwo,squares,sumOf), 0, one2ten);
full script online
Your more operates with only 2 functions. And the problem is here more(squares,divtwo)(sumOf) you execute a function, and here more(squares,divtwo, sumOf) you return a function which expects another call (fo example const f = more(squares,divtwo, sumOf); f(args)).
In order to have a variable number of composable functions you can define a different more for functions composition. Regular way of composing any number of functions is compose or pipe functions (the difference is arguments order: pipe takes functions left-to-right in execution order, compose - the opposite).
Regular way of defining pipe or compose:
const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x);
const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);
You can change x to (...args) to match your more definition.
Now you can execute any number of functions one by one:
const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x);
const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);
const inc = x => x + 1;
const triple = x => x * 3;
const log = x => { console.log(x); return x; } // log x, then return x for further processing
// left to right application
const pipe_ex = pipe(inc, log, triple, log)(10);
// right to left application
const compose_ex = compose(log, inc, log, triple)(10);
I still can't replace compose(divtwo,squares)(sumOf) with compose(divtwo,squares,sumOf)
Yes, they are not equivalent. And you shouldn't try anyway! Notice that divtwo and squares are transducers, while sumOf is a reducer. They have different types. Don't build a more function that mixes them up.
If you insist on using a dynamic number of transducers, put them in an array:
[divtwo, squares].reduceRight((t, r) => t(r), sumOf)
I'm attempting to implement functors in Javascript without using container types ([]/{}). Hence, I solely utilize pure higher order functions to construct them:
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const none = option(null);
const isAssigned = x => x !== null && x !== undefined;
const inc = x => x + 1;
const sqr = x => x * x;
const head = xs => xs[0]
const log = x => (console.log(x), x);
option(head([4])) (inc) (sqr) (log); // 25
option(head([])) (inc) (sqr) (log); // not executed
option takes a value and a pure function, lifts the function into its context, applies it to the value and returns the result in the same context. I guess it is a functor. However, it doesn't follow the functor prototcol in Javascript, that each functor must own a map function on its prototype.
option can apparently be extended to a monad-like type (at least it behaves like one in my example):
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const option_ = x => f => isAssigned(x) ? flatten(option(f(x))) : none;
const none = option(null);
const of = x => option(x); // return
const flatten = F => { // it gets a bit ugly here
let y;
F(z => (y = z, z));
return y;
};
// auxiliary functions
const compn = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);
const getOrElse = x => F => {
let y;
F(z => (y = z, z));
return isAssigned(y) ? y : x;
};
const isAssigned = x => x !== null && x !== undefined;
const log = prefix => x => (console.log(prefix, x), x);
const head = xs => xs[0];
const head_ = xs => option(xs[0]);
const sqr = x => x * x;
// some data
const xs = [5],
ys = [];
// run
const w = option(xs) (head),
x = option(ys) (head),
y = option_(xs) (head_),
z = option_(ys) (head_);
log("square head of xs:") (compn(sqr, getOrElse(1)) (w)); // 25
log("square head of ys:") (compn(sqr, getOrElse(0)) (x)); // 0
log("square head_ of xs:") (compn(sqr, getOrElse(0)) (y)); // 25
log("square head_ of ys:") (compn(sqr, getOrElse(0)) (z)); // 0
Provided option is actually a functor my question is: Is it possible to express every functor/monad solely with (pure) higher order functions, where the results of contextual (or effectful) computations are held in the call stack?
Of course. Aside from a couple JS primitives (*, +, Number, and String) to demonstrate functionality, below you will only see:
variables
lambda abstractions
applications
const identity = x => x
const fromNullable = x => x == null ? None : Option(x)
const Option = (value) => k => {
const join = () => value
const map = f => Option(f(value))
const bind = f => f(value)
const ap = m => optionMap(value)(m)
const fold = f => f(value)
return k (value, join, map, bind, ap, fold)
}
const None = () => k => {
const join = identity
const map = f => None()
const bind = f => None()
const ap = m => None()
const fold = f => f(null)
return k (null, join, map, bind, ap, fold)
}
const optionJoin = m => m((value, join, map, bind, ap, fold) => join())
const optionMap = f => m => m((value, join, map, bind, ap, fold) => map(f))
const optionBind = f => m => m((value, join, map, bind, ap, fold) => bind(f))
const optionAp = n => m => m((value, join, map, bind, ap, fold) => ap(n))
const optionFold = f => m => m((value, join, map, bind, ap, fold) => fold(f))
optionFold (console.log) (Option(5)) // 5
optionFold (console.log) (None()) // null
optionFold (console.log) (optionMap (x => x * 2) (Option(5))) // 10
optionFold (console.log) (optionMap (x => x * 2) (None()))// null
optionFold (console.log) (optionAp(Option(3)) (Option(x => x + 4))) // 7
optionFold (console.log) (optionAp(Option(3)) (None())) // null
optionFold (console.log) (optionBind(x => Option(x * x)) (Option(16))) // 256
optionFold (console.log) (optionBind(x => Option(x * x)) (None())) // null
optionFold (console.log) (optionJoin (Option(Option('hi')))) // 'hi'
optionFold (console.log) (optionJoin (Option(None())))// null
We can skip some intermediate assignments and write it using only single-param lambdas -
const Option = value => k => // unit
k (_ => value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(value)(m)) // ap
(f => f(value)) // fold
const None = k => // unit
k (_ => None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
const log =
fold(console.log)
log(Option(5)) // 5
log(None) // null
log(map (x => x * 2)(Option(5))) // 10
log(map (x => x * 2)(None)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(x => Option(x * x))(Option(16))) // 256
log(bind(x => Option(x * x))(None)) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null
Maybe this help us see the pattern m(_ => _ => ... => ?) in the join, map, bind, ap, fold functions -
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
This can be abstracted as a generic function. arity(len) returns a function, arg(n), that returns a curried function with len parameters, that returns the nth argument. This is more plainly demonstrated in code -
arity(3)(0)
// (x => _ => _ => x)
arity(4)(0)
// (x => _ => _ => _ => x)
arity(4)(1)
// (_ => x => _ => _ => x)
arity(4)(2)
// (_ => _ => x => _ => x)
arity(5)(3)
// (_ => _ => _ => x => _ => x)
We can implement arity like so -
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
Now we can easily write the public api functions as simple argument selectors -
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const arg = arity(5)
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
There's nothing more to it :D Expand the snippet below to see verify the results in your own browser -
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const arg = arity(5)
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
const log = x =>
fold(x)(console.log)
log(Option(5)) // 5
log(None) // null
log(map(Option(5))(x => x * 2)) // 10
log(map(None)(x => x * 2)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(Option(16))(x => Option(x * x))) // 256
log(bind(None)(x => Option(x * x))) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null