Similar curry functions producing different results - javascript

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;
}

Related

Compose method in recompose Library

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'

Reversing the order of default arguments in JavaScript

I have the following recursive compose function:
const compose = (f, n = 1) => n > 1 ?
compose(compose(f), n - 1) :
g => x => f(g(x));
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = compose(length, 2)(filter);
const odd = n => n % 2 === 1;
console.log(countWhere(odd)([1,2,3,4,5,6,7,8,9])); // 5
Now, what I'd like to do is flip the arguments of compose so that the default argument is first:
const compose = (n = 1, f) => n > 1 ? // wishful thinking
compose(n - 1, compose(f)) : // compose(f) is the same as compose(1, f)
g => x => f(g(x));
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = compose(2, length)(filter); // I want to call it like this
const odd = n => n % 2 === 1;
console.log(countWhere(odd)([1,2,3,4,5,6,7,8,9])); // 5
What's the most elegant way to write such functions where the default arguments come first?
Edit: I actually want to create the map and ap methods of functions of various arities, so that I can write:
const length = a => a.length;
const filter = p => a => a.filter(p);
const countWhere = length.map(2, filter); // length <$> filter
const pair = x => y => [x, y];
const add = x => y => x + y;
const mul = x => y => x * y;
const addAndMul = pair.map(2, add).ap(2, mul); // (,) <$> (+) <*> (*)
Hence, I'd rather not curry the methods as Bergi suggested in his answer.
For more information, read: Is implicit wrapping and unwrapping of newtypes in Haskell a sound idea?
I would recommend to just not overload your functions or use default parameters:
const compose = n => f => n > 1
? compose(n - 1)(composeOne(f))
: g => x => f(g(x));
const composeOne = compose(1);
In this case you could probably also just inline it, as it seems composeOne wouldn't be called anywhere else:
const compose = n => f => n > 1
? compose(n - 1)(compose(1)(f))
: g => x => f(g(x));
Or even not do a recursive call at all, but always create the g => x => … lambda and transform it conditionally:
const compose = n => f => {
const l = g => x => f(g(x));
return n > 1 ? compose(n - 1)(l) : l;
};
// same without temporary variables:
const id = x => x;
const compose = n => f => (n > 1 ? compose(n-1) : id)(g => x => f(g(x)))
What's the most elegant way to write such functions where the default arguments come first?
Using only default initialisers requires some arcane hackery:
function demo(n, f = [n, n = 1][0]) {
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 1
The most straightforward way would be destructuring with a conditional operator:
function demo(...args) {
const [n, f] = args.length < 2 ? [1, ...args] : args;
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 0
More in the spirit of "reversing the order of arguments" might be doing that literally:
function demo(...args) {
const [f, n = 1] = args.reverse();
console.log(n, f);
}
demo(2, "f"); // 2 f
demo("g"); // 1 g
console.log(demo.length) // 0
The latter two attempts have the drawback of requiring an extra declaration (preventing us from using concise arrow functions) and also don't reflect the actual number or required parameters in the .length.

Extracting data from a function chain without arrays

This is an advanced topic of
How to store data of a functional chain of Monoidal List?
I am pretty sure we can somehow extract data from a function chain without using an array storing data.
The basic structure is :
L = a => L
very simple, but this structure generates a list:
L(1)(2)(3)(4)(5)(6)()
This may be related to
What is a DList?
, but this structure strictly depends on function chain only.
So, what is the way to pull out the whole values?
Current achievement of mine merely pulling out the head and tail, and I don't know how to fix this.
EDIT:
I forgot to mention what I try to do is
List.fold(f) / reduce(f)
operation.
So, if one choses f as Array.concat which means you can extract data as an array, but simply fold is not limited to array concatenation. and f can be add/sum etc.
So, currently, so far, to visualize the internal behavior, in a sense, I write log as f.
EDIT2
I must clarify more. The specification can be presented:
const f = (a) => (b) => a + b;//binary operation
A(a)(b)(f) = f(a)(b) // a + b
A(a)(b)(c)(f) = f(f(a)(b))(c) // a + b + c
So this is exactly
(a b c).reduce(f)
thing, and when
f = (a) => (b) => a.concat(b)
The result would be [a, b, c].
Array.concat is merely a member of generalized binary operations f.
At first this challenge is easy for my skill, but turned out hard and felt it's better to ask smarter coder.
Thanks.
const A = a => {
const B = b => (b === undefined)
? (() => {
log("a " + a);
return A();
})()
: c => (c === undefined)
? (() => {
log("b " + b);
return B()();
})()
: B;
return B;
};
A(1)(2)(3)(4)(5)(6)()
function log(m) {
console.log((m)); //IO
return m;
};
result:
b 6
a 1
a undefined
Quite the series of questions you have here. Here's my take on it:
We start with a way to construct lists
nil is a constant which represents the empty list
cons (x, list) constructs a new list with x added to the front of list
// nil : List a
const nil =
(c, n) => n
// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
(c, n) => c (y (c, n), x)
// list : List Number
const myList =
cons (1, cons (2, cons (3, cons (4, nil))))
console.log (myList ((x, y) => x + y, 0))
// 10
And to satisfy your golfy variadic curried interface, here is autoCons
const autoCons = (init, n) =>
{ const loop = acc => (x, n) =>
isFunction (x)
? acc (x, n)
: loop (cons (x, acc))
return loop (nil) (init, n)
}
const isFunction = f =>
f != null && f.constructor === Function && f.length === 2
const nil =
(c, n) => n
const cons = (x, y = nil) =>
(c, n) => c (y (c, n), x)
console.log
( autoCons (1) ((x,y) => x + y, 0) // 1
, autoCons (1) (2) ((x,y) => x + y, 0) // 3
, autoCons (1) (2) (3) ((x,y) => x + y, 0) // 6
, autoCons (1) (2) (3) (4) ((x,y) => x + y, 0) // 10
)
Our encoding makes it possible to write other generic list functions, like isNil
// isNil : List a -> Bool
const isNil = l =>
l ((acc, _) => false, true)
console.log
( isNil (autoCons (1)) // false
, isNil (autoCons (1) (2)) // false
, isNil (nil) // true
)
Or like length
// length : List a -> Int
const length = l =>
l ((acc, _) => acc + 1, 0)
console.log
( length (nil) // 0
, length (autoCons (1)) // 1
, length (autoCons (1) (2)) // 2
, length (autoCons (1) (2) (3)) // 3
)
Or nth which fetches the nth item in the list
// nth : Int -> List a -> a
const nth = n => l =>
l ( ([ i, res ], x) =>
i === n
? [ i + 1, x ]
: [ i + 1, res]
, [ 0, undefined ]
) [1]
console.log
( nth (0) (autoCons ("A") ("B") ("C")) // "A"
, nth (1) (autoCons ("A") ("B") ("C")) // "B"
, nth (2) (autoCons ("A") ("B") ("C")) // "C"
, nth (3) (autoCons ("A") ("B") ("C")) // undefined
)
We can implement functions like map and filter for our list
// map : (a -> b) -> List a -> List b
const map = f => l =>
l ( (acc, x) => cons (f (x), acc)
, nil
)
// filter : (a -> Bool) -> List a -> List a
const filter = f => l =>
l ( (acc, x) => f (x) ? cons (x, acc) : acc
, nil
)
We can even make a program using our list which takes a list as an argument
// rcomp : (a -> b) -> (b -> c) -> a -> c
const rcomp = (f, g) =>
x => g (f (x))
// main : List String -> String
const main = letters =>
autoCons (map (x => x + x))
(filter (x => x !== "dd"))
(map (x => x.toUpperCase()))
(rcomp, x => x)
(letters)
((x, y) => x + y, "")
main (autoCons ("a") ("b") ("c") ("d") ("e"))
// AABBCCEE
Run the program in your browser below
const nil =
(c, n) => n
const cons = (x, y = nil) =>
(c, n) => c (y (c, n), x)
const isFunction = f =>
f != null && f.constructor === Function && f.length === 2
const autoCons = (init, n) =>
{ const loop = acc => (x, n) =>
isFunction (x)
? acc (x, n)
: loop (cons (x, acc))
return loop (nil) (init, n)
}
const map = f => l =>
l ( (acc, x) => cons (f (x), acc)
, nil
)
const filter = f => l =>
l ( (acc, x) => f (x) ? cons (x, acc) : acc
, nil
)
const rcomp = (f, g) =>
x => g (f (x))
const main = letters =>
autoCons (map (x => x + x))
(filter (x => x !== "dd"))
(map (x => x.toUpperCase()))
(rcomp, x => x)
(letters)
((x, y) => x + y, "")
console.log (main (autoCons ("a") ("b") ("c") ("d") ("e")))
// AABBCCEE
Sorry, my bad
Let's rewind and look at our initial List example
// list : List Number
const myList =
cons (1, cons (2, cons (3, cons (4, nil))))
console.log
( myList ((x, y) => x + y, 0) // 10
)
We conceptualize myList as a list of numbers, but we contradict ourselves by calling myList (...) like a function.
This is my fault. In trying to simplify the example, I crossed the barrier of abstraction. Let's look at the types of nil and cons –
// nil : List a
// cons : (a, List a) -> List a
Given a list of type List a, how do we get a value of type a out? In the example above (repeated below) we cheat by calling myList as a function. This is internal knowledge that only the implementer of nil and cons should know
// myList is a list, not a function... this is confusing...
console.log
( myList ((x, y) => x + y, 0) // 10
)
If you look back at our original implementation of List,
// nil : List a
const nil =
(c, n) => n
// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
(c, n) => c (y (c, n), x)
I also cheated you giving simplified type annotations like List a. What is List, exactly?
We're going to address all of this and it starts with our implementation of List
List, take 2
Below nil and cons have the exact same implementation. I've only fixed the type annotations. Most importantly, I added reduce which provides a way to get values "out" of our list container.
The type annotation for List is updated to List a r – this can be understood as "a list containing values of type a that when reduced, will produce a value of type r."
// type List a r = (r, a) -> r
// nil : List a r
const nil =
(c, n) => n
// cons : (a, List a r) -> List a r
const cons = (x, y = nil) =>
(c, n) => c (y (c, n), x)
// reduce : ((r, a) -> r, r) -> List a -> r
const reduce = (f, init) => l =>
l (f, init)
Now we can maintain List as a sane type, and push all the wonky behavior you want into the autoCons function. Below we update autoCons to work with our list acc using our new reduce function
const autoCons = (init, n) =>
{ const loop = acc => (x, n) =>
isFunction (x)
// don't break the abstraction barrier
? acc (x, n)
// extract the value using our appropriate list module function
? reduce (x, n) (acc)
: loop (cons (x, acc))
return loop (nil) (init, n)
}
So speaking of types, let's examine the type of autoCons –
autoCons (1) // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3) // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3) (add, 0) // 6
Well autoCons always returns a lambda, but that lambda has a type that we cannot determine – sometimes it returns another lambda of its same kind, other times it returns a completely different result; in this case some number, 6
Because of this, we cannot easily mix and combine autoCons expressions with other parts of our program. If you drop this perverse drive to create variadic curried interfaces, you can make an autoCons that is type-able
// autoCons : (...a) -> List a r
const autoCons = (...xs) =>
{ const loop = (acc, x = nil, ...xs) =>
x === nil
? acc
: loop (cons (x, acc), ...xs)
return loop (nil, ...xs)
}
Because autoCons now returns a known List (instead of the mystery unknown type caused by variadic currying), we can plug an autoCons list into the various other functions provided by our List module.
const c =
autoCons (1, 2, 3)
const d =
autoCons (4, 5, 6)
console.log
( toArray (c) // [ 1, 2, 3 ]
, toArray (map (x => x * x) (d)) // [ 16, 25, 36 ]
, toArray (filter (x => x != 5) (d)) // [ 4, 6 ]
, toArray (append (c, d)) // [ 1, 2, 3, 4, 5, 6 ]
)
These kind of mix-and-combine expressions is not possible when autoCons returns a type we cannot rely upon. Another important thing to notice is the List module gives us a place to expand its functionality. We can easily add functions used above like map, filter, append, and toArray – you lose this flexibility when trying to shove everything through the variadic curried interface
Let's look at those additions to the List module now – as you can see, each function is well-typed and has behavior we can rely upon
// type List a r = (r, a) -> r
// nil : List a r
// cons : (a, List a r) -> List a r
// reduce : ((r, a) -> r, r) -> List a r -> r
// length : List a r -> Int
const length =
reduce
( (acc, _) => acc + 1
, 0
)
// map : (a -> b) -> List a r -> List b r
const map = f =>
reduce
( (acc, x) => cons (f (x), acc)
, nil
)
// filter : (a -> Bool) -> List a r -> List a r
const filter = f =>
reduce
( (acc,x) => f (x) ? cons (x, acc) : acc
, nil
)
// append : (List a r, List a r) -> List a r
const append = (l1, l2) =>
(c, n) =>
l2 (c, l1 (c, n))
// toArray : List a r -> Array a
const toArray =
reduce
( (acc, x) => [ ...acc, x ]
, []
)
Even autoCons makes sense as part of our module now
// autoCons : (...a) -> List a r
const autoCons = (...xs) =>
{ const loop = (acc, x = nil, ...xs) =>
x === nil
? acc
: loop (cons (x, acc), ...xs)
return loop (nil, ...xs)
}
Add any other functions to the List module
// nth: Int -> List a r -> a
// isNil : List a r -> Bool
// first : List a r -> a
// rest : List a r -> List a r
// ...
Given an expression like A(a)(b)(f) where f is a function, it's impossible to know whether f is supposed to be added to the list or whether it's the reducing function. Hence, I'm going to describe how to write expressions like A(a)(b)(f, x) which is equivalent to [a, b].reduce(f, x). This allows us to distinguish when the list ends depending upon how many arguments you provide:
const L = g => function (x, a) {
switch (arguments.length) {
case 1: return L(k => g((f, a) => k(f, f(a, x))));
case 2: return g((f, a) => a)(x, a);
}
};
const A = L(x => x);
const xs = A(1)(2)(3)(4)(5);
console.log(xs((x, y) => x + y, 0)); // 15
console.log(xs((x, y) => x * y, 1)); // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]
It works due to continuations. Every time we add a new element, we accumulate a CPS function. Each CPS function calls the previous CPS function, thereby creating a CPS function chain. When we give this CPS function chain a base function, it unrolls the chain and allows us to reduce it. It's the same idea behind transducers and lenses.
Edit: user633183's solution is brilliant. It uses the Church encoding of lists using right folds to alleviate the need for continuations, resulting in simpler code which is easy to understand. Here's her solution, modified to make foldr seem like foldl:
const L = g => function (x, a) {
switch (arguments.length) {
case 1: return L((f, a) => f(g(f, a), x));
case 2: return g(x, a);
}
};
const A = L((f, a) => a);
const xs = A(1)(2)(3)(4)(5);
console.log(xs((x, y) => x + y, 0)); // 15
console.log(xs((x, y) => x * y, 1)); // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]
Here g is the Church encoded list accumulated so far. Initially, it's the empty list. Calling g folds it from the right. However, we also build the list from the right. Hence, it seems like we're building the list and folding it from the left because of the way we write it.
If all these functions are confusing you, what user633183 is really doing is:
const L = g => function (x, a) {
switch (arguments.length) {
case 1: return L([x].concat(g));
case 2: return g.reduceRight(x, a);
}
};
const A = L([]);
const xs = A(1)(2)(3)(4)(5);
console.log(xs((x, y) => x + y, 0)); // 15
console.log(xs((x, y) => x * y, 1)); // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]
As you can see, she is building the list backwards and then using reduceRight to fold the backwards list backwards. Hence, it looks like you're building and folding the list forwards.
I'll have to admit I haven't read through your linked questions and I'm mainly here for the fun puzzle... but does this help in any way?
I figured you want to differentiate between adding an element (calling with a new value) and running a function on the list (calling with a function). Since I had to somehow pass the function to run, I couldn't get the (1) vs () syntax to work.
This uses an interface that returns an object with concat to extend the list, and fold to run a reducer on the list. Again, not sure if it's a complete answer, but it might help you explore other directions.
const Empty = Symbol();
const L = (x, y = Empty) => ({
concat: z => L(z, L(x, y)),
fold: (f, seed) => f(x, y === Empty ? seed : y.fold(f, seed))
});
const sum = (a, b) => a + b;
console.log(
L(1)
.concat(2).concat(3).concat(4).concat(5).concat(6)
.fold(sum, 0)
)
Work in progress
Thanks to the stunning contribution by #user3297291 , I somehow could refactor the code to fit my specification, but not working because I am lost the concept during the implementation :(
The point is whole thing must be curried, and no object.method is involved.
Can anyone "debug" please :)
The initial value is set to the first element, in this example as 1
I think this is almost done.
const isFunction = f => (typeof f === 'function');
const Empty = Symbol();
const L = (x = Empty) => (y = Empty) => z => isFunction(z)
? (() => {
const fold = f => seed => f(x)(y) === Empty
? seed
: (L)(y)(f);
return fold(z)(x);
})()
: L(z)(L(x)(y));
const sum = a => b => a + b;
console.log(
L(1)(2)(3)(4)(5)(6)(sum)
);
Output
z => isFunction(z)
? (() => {
const fold = f => seed => f(x)(y) === Empty
? seed
: (L)(y)(f);
return fold(z)(x);
})()
: L(z)(L(x)(y))
I've gone through the various questions you have but I'm still not sure I entirely understand what you're looking for. On the off chance you're simply looking to represent a linked list, here is a "dumb" representation that does not use clever tricks like overloaded arguments or default parameter values:
const List = (() => {
const nil = Symbol()
// ADT
const Nil = nil
const Cons = x => xs => ({ x, xs })
const match = ({ Nil, Cons }) => l => l === nil ? Nil : Cons(l.x)(l.xs)
// Functor
const map = f => match({
Nil,
Cons: x => xs => Cons(f(x))(map(f)(xs))
})
// Foldable
const foldr = f => z => match({
Nil: z,
Cons: x => xs => f(x)(foldr(f)(z)(xs)) // danger of stack overflow!
// https://wiki.haskell.org/Foldr_Foldl_Foldl%27
})
return { Nil, Cons, match, map, foldr }
})()
const { Nil, Cons, match, map, foldr } = List
const toArray = foldr(x => xs => [x, ...xs])([])
const l = Cons(1)(Cons(2)(Cons(3)(Nil)))
const l2 = map(x => x * 2)(l)
const l3 = map(x => x * 3)(l2)
const a = toArray(l3)
console.log(a) // => [6, 12, 18]

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)

How to chain map and filter functions in the correct order

I really like chaining Array.prototype.map, filter and reduce to define a data transformation. Unfortunately, in a recent project that involved large log files, I could no longer get away with looping through my data multiple times...
My goal:
I want to create a function that chains .filter and .map methods by, instead of mapping over an array immediately, composing a function that loops over the data once. I.e.:
const DataTransformation = () => ({
map: fn => (/* ... */),
filter: fn => (/* ... */),
run: arr => (/* ... */)
});
const someTransformation = DataTransformation()
.map(x => x + 1)
.filter(x => x > 3)
.map(x => x / 2);
// returns [ 2, 2.5 ] without creating [ 2, 3, 4, 5] and [4, 5] in between
const myData = someTransformation.run([ 1, 2, 3, 4]);
My attempt:
Inspired by this answer and this blogpost I started writing a Transduce function.
const filterer = pred => reducer => (acc, x) =>
pred(x) ? reducer(acc, x) : acc;
const mapper = map => reducer => (acc, x) =>
reducer(acc, map(x));
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
The problem:
The problem with the Transduce snippet above, is that it runs “backwards”... The last method I chain is the first to be executed:
const someTransformation = Transduce()
.map(x => x + 1)
.filter(x => x > 3)
.map(x => x / 2);
// Instead of [ 2, 2.5 ] this returns []
// starts with (x / 2) -> [0.5, 1, 1.5, 2]
// then filters (x < 3) -> []
const myData = someTransformation.run([ 1, 2, 3, 4]);
Or, in more abstract terms:
Go from:
Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, f(g(x)))
To:
Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, g(f(x)))
Which is similar to:
mapper(f) (mapper(g) (concat))
I think I understand why it happens, but I can't figure out how to fix it without changing the “interface” of my function.
The question:
How can I make my Transduce method chain filter and map operations in the correct order?
Notes:
I'm only just learning about the naming of some of the things I'm trying to do. Please let me know if I've incorrectly used the Transduce term or if there are better ways to describe the problem.
I'm aware I can do the same using a nested for loop:
const push = (acc, x) => (acc.push(x), acc);
const ActionChain = (actions = []) => {
const run = arr =>
arr.reduce((acc, x) => {
for (let i = 0, action; i < actions.length; i += 1) {
action = actions[i];
if (action.type === "FILTER") {
if (action.fn(x)) {
continue;
}
return acc;
} else if (action.type === "MAP") {
x = action.fn(x);
}
}
acc.push(x);
return acc;
}, []);
const addAction = type => fn =>
ActionChain(push(actions, { type, fn }));
return {
map: addAction("MAP"),
filter: addAction("FILTER"),
run
};
};
// Compare to regular chain to check if
// there's a performance gain
// Admittedly, in this example, it's quite small...
const naiveApproach = {
run: arr =>
arr
.map(x => x + 3)
.filter(x => x % 3 === 0)
.map(x => x / 3)
.filter(x => x < 40)
};
const actionChain = ActionChain()
.map(x => x + 3)
.filter(x => x % 3 === 0)
.map(x => x / 3)
.filter(x => x < 40)
const testData = Array.from(Array(100000), (x, i) => i);
console.time("naive");
const result1 = naiveApproach.run(testData);
console.timeEnd("naive");
console.time("chain");
const result2 = actionChain.run(testData);
console.timeEnd("chain");
console.log("equal:", JSON.stringify(result1) === JSON.stringify(result2));
Here's my attempt in a stack snippet:
const filterer = pred => reducer => (acc, x) =>
pred(x) ? reducer(acc, x) : acc;
const mapper = map => reducer => (acc, x) => reducer(acc, map(x));
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
const sameDataTransformation = Transduce()
.map(x => x + 5)
.filter(x => x % 2 === 0)
.map(x => x / 2)
.filter(x => x < 4);
// It's backwards:
// [-1, 0, 1, 2, 3]
// [-0.5, 0, 0.5, 1, 1.5]
// [0]
// [5]
console.log(sameDataTransformation.run([-1, 0, 1, 2, 3, 4, 5]));
before we know better
I really like chaining ...
I see that, and I'll appease you, but you'll come to understand that forcing your program through a chaining API is unnatural, and more trouble than it's worth in most cases.
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
map: map => Transduce(mapper(map)(reducer)),
filter: pred => Transduce(filterer(pred)(reducer)),
run: arr => arr.reduce(reducer, [])
});
I think I understand why it happens, but I can't figure out how to fix it without changing the “interface” of my function.
The problem is indeed with your Transduce constructor. Your map and filter methods are stacking map and pred on the outside of the transducer chain, instead of nesting them inside.
Below, I've implemented your Transduce API that evaluates the maps and filters in correct order. I've also added a log method so that we can see how Transduce is behaving
const Transduce = (f = k => k) => ({
map: g =>
Transduce(k =>
f ((acc, x) => k(acc, g(x)))),
filter: g =>
Transduce(k =>
f ((acc, x) => g(x) ? k(acc, x) : acc)),
log: s =>
Transduce(k =>
f ((acc, x) => (console.log(s, x), k(acc, x)))),
run: xs =>
xs.reduce(f((acc, x) => acc.concat(x)), [])
})
const foo = nums => {
return Transduce()
.log('greater than 2?')
.filter(x => x > 2)
.log('\tsquare:')
.map(x => x * x)
.log('\t\tless than 30?')
.filter(x => x < 30)
.log('\t\t\tpass')
.run(nums)
}
// keep square(n), forall n of nums
// where n > 2
// where square(n) < 30
console.log(foo([1,2,3,4,5,6,7]))
// => [ 9, 16, 25 ]
untapped potential
Inspired by this answer ...
In reading that answer I wrote, you overlook the generic quality of Trans as it was written there. Here, our Transduce only attempts to work with Arrays, but really it can work with any type that has an empty value ([]) and a concat method. These two properties make up a category called Monoids and we'd be doing ourselves a disservice if we didn't take advantage of transducer's ability to work with any type in this category.
Above, we hard-coded the initial accumulator [] in the run method, but this should probably be supplied as an argument – much like we do with iterable.reduce(reducer, initialAcc)
Aside from that, both implementations are essentially equivalent. The biggest difference is that the Trans implementation provided in the linked answer is Trans itself is a monoid, but Transduce here is not. Trans neatly implements composition of transducers in the concat method whereas Transduce (above) has composition mixed within each method. Making it a monoid allows us to rationalize Trans the same way do all other monoids, instead of having to understand it as some specialized chaining interface with unique map, filter, and run methods.
I would advise building from Trans instead of making your own custom API
have your cake and eat it too
So we learned the valuable lesson of uniform interfaces and we understand that Trans is inherently simple. But, you still want that sweet chaining API. OK, ok...
We're going to implement Transduce one more time, but this time we'll do so using the Trans monoid. Here, Transduce holds a Trans value instead of a continuation (Function).
Everything else stays the same – foo takes 1 tiny change and produces an identical output.
// generic transducers
const mapper = f =>
Trans(k => (acc, x) => k(acc, f(x)))
const filterer = f =>
Trans(k => (acc, x) => f(x) ? k(acc, x) : acc)
const logger = label =>
Trans(k => (acc, x) => (console.log(label, x), k(acc, x)))
// magic chaining api made with Trans monoid
const Transduce = (t = Trans.empty()) => ({
map: f =>
Transduce(t.concat(mapper(f))),
filter: f =>
Transduce(t.concat(filterer(f))),
log: s =>
Transduce(t.concat(logger(s))),
run: (m, xs) =>
transduce(t, m, xs)
})
// when we run, we must specify the type to transduce
// .run(Array, nums)
// instead of
// .run(nums)
Expand this code snippet to see the final implementation – of course you could skip defining a separate mapper, filterer, and logger, and instead define those directly on Transduce. I think this reads nicer tho.
// Trans monoid
const Trans = f => ({
runTrans: f,
concat: ({runTrans: g}) =>
Trans(k => f(g(k)))
})
Trans.empty = () =>
Trans(k => k)
const transduce = (t, m, xs) =>
xs.reduce(t.runTrans((acc, x) => acc.concat(x)), m.empty())
// complete Array monoid implementation
Array.empty = () => []
// generic transducers
const mapper = f =>
Trans(k => (acc, x) => k(acc, f(x)))
const filterer = f =>
Trans(k => (acc, x) => f(x) ? k(acc, x) : acc)
const logger = label =>
Trans(k => (acc, x) => (console.log(label, x), k(acc, x)))
// now implemented with Trans monoid
const Transduce = (t = Trans.empty()) => ({
map: f =>
Transduce(t.concat(mapper(f))),
filter: f =>
Transduce(t.concat(filterer(f))),
log: s =>
Transduce(t.concat(logger(s))),
run: (m, xs) =>
transduce(t, m, xs)
})
// this stays exactly the same
const foo = nums => {
return Transduce()
.log('greater than 2?')
.filter(x => x > 2)
.log('\tsquare:')
.map(x => x * x)
.log('\t\tless than 30?')
.filter(x => x < 30)
.log('\t\t\tpass')
.run(Array, nums)
}
// output is exactly the same
console.log(foo([1,2,3,4,5,6,7]))
// => [ 9, 16, 25 ]
wrap up
So we started with a mess of lambdas and then made things simpler using a monoid. The Trans monoid provides distinct advantages in that the monoid interface is known and the generic implementation is extremely simple. But we're stubborn or maybe we have goals to fulfill that are not set by us – we decide to build the magic Transduce chaining API, but we do so using our rock-solid Trans monoid which gives us all the power of Trans but also keeps complexity nicely compartmentalised.
dot chaining fetishists anonymous
Here's a couple other recent answers I wrote about method chaining
Is there any way to make a functions return accessible via a property?
Chaining functions and using an anonymous function
Pass result of functional chain to function
I think you need to change the order of your implementations:
const filterer = pred => reducer => (x) =>pred((a=reducer(x) )?x: undefined;
const mapper = map => reducer => (x) => map(reducer(x));
Then you need to change the run command to:
run: arr => arr.reduce((a,b)=>a.concat([reducer(b)]), []);
And the default reducer must be
x=>x
However, this way the filter wont work. You may throw undefined in the filter function and catch in the run function:
run: arr => arr.reduce((a,b)=>{
try{
a.push(reducer(b));
}catch(e){}
return a;
}, []);
const filterer = pred => reducer => (x) =>{
if(!pred((a=reducer(x))){
throw undefined;
}
return x;
};
However, all in all i think the for loop is much more elegant in this case...

Categories