Related
Two days ago, I announced a preview release of Underscore that integrates with the new Node.js way of natively supporting ES modules.1 Yesterday, somebody responded on Twitter with the following question:
Can you do Ramda-style data last functions?
He or she was referring to one of the main differences between Underscore and Ramda. In Underscore, functions typically take the data to be operated on as the first parameter, while Ramda takes them as the last parameter:
import _ from 'underscore';
import * as R from 'ramda';
const square = x => x * x;
// Underscore
_.map([1, 2, 3], square); // [1, 4, 9]
// Ramda
R.map(square, [1, 2, 3]); // [1, 4, 9]
The idea behind the data-last order in Ramda is that when doing partial application, the data argument is often supplied last. Taking the data as the last parameter removes the need for a placeholder in such cases:
// Let's create a function that maps `square` over its argument.
// Underscore
const mapSquare = _.partial(_.map, _, square);
// Ramda with explicit partial application
const mapSquare = R.partial(R.map, [square]);
// Ramda, shorter notation through automatic currying
const mapSquare = R.map(square);
// Ramda with currying and placeholder if it were data-first
const mapSquare = R.map(R.__, square)
// Behavior in all cases
mapSquare([1, 2, 3]); // [1, 4, 9]
mapSquare([4, 5, 6]); // [16, 25, 36]
As the example shows, it is especially the curried notation that makes data-last attractive for such scenarios.
Why doesn't Underscore do this? There are several reasons for that, which I put in a footnote.2 Nevertheless, making Underscore behave like Ramda is an interesting exercise in functional programming. In my answer below, I'll show how you can do this in just a few lines of code.
1 At the time of writing, if you want to try it, I recommend installing underscore#preview from NPM. This ensures that you get the latest preview version. I just published a fix that bumped the version to 1.13.0-1. I will release 1.13.0 as underscore#latest some time in the near future.
2 Reasons for Underscore to not implement data-last or currying:
Underscore was born when Jeremy Ashkenas factored out common patterns from DocumentCloud (together with Backbone). As it happens, neither data-last partial application nor currying were common patterns in that application.
Changing Underscore from data-first to data-last would break a lot of code.
It is not a universal rule that data are supplied last in partial application; supplying the data first is equally imaginable. Thus, data-last isn't fundamentally better, it's just making a different tradeoff.
While currying is nice, it also has some disadvantages: it adds overhead and it fixes the arity of a function (unless you make the function lazy, which adds more overhead). Underscore works more with optional and variadic arguments than Ramda, and also prefers making features that add overhead opt-in instead of enabling them by default.
Taking the question very literally, let's just start with a function that transforms a data-first function into a data-last function:
const dataLast = f => _.restArguments(function(args) {
args.unshift(args.pop());
return f.apply(this, args);
});
const dataLastMap = dataLast(_.map);
dataLastMap(square, [1, 2, 3]); // [1, 4, 9]
We could map dataLast over Underscore to get a data-last version of the entire library:
const L = _.mapObject(_, dataLast);
const isOdd = x => x % 2;
L.map(square, [1, 2, 3]); // [1, 4, 9]
L.filter(isOdd, [1, 2, 3]); // [1, 3]
However, we can do better. Ramda-style currying is not too hard to implement, either:
const isPlaceholder = x => x === _;
function curry(f, arity = f.length, preArgs = []) {
const applied = _.partial.apply(null, [f].concat(preArgs));
return _.restArguments(function(args) {
const supplied = _.countBy(args, isPlaceholder)['false'];
if (supplied < arity) {
return curry(applied, arity - supplied, args);
} else {
return applied.apply(null, args);
}
});
}
With just a little bit of extra sophistication, we can even correctly support this bindings:
function curry(f, arity = f.length, preArgs = [], thisArg) {
if (!_.isUndefined(thisArg)) f = f.bind(thisArg);
const applied = _.partial.apply(null, [f].concat(preArgs));
return _.restArguments(function(args) {
const supplied = _.countBy(args, isPlaceholder)['false'];
if (supplied < arity) {
return curry(applied, arity - supplied, args, this);
} else {
return applied.apply(this, args);
}
});
}
Currying by itself is independent of whether you do data-first or data-last. Here's a curried version of _.map that is still data-first:
const curriedMap = curry(_.map);
curriedMap([1, 2, 3], square, null);
curriedMap([1, 2, 3])(square, null);
curriedMap([1, 2, 3])(square)(null);
curriedMap([1, 2, 3], square)(null);
curriedMap([1, 2, 3], _, null)(square);
curriedMap(_, _, null)([1, 2, 3], square);
curriedMap(_, _, null)(_, square)([1, 2, 3]);
curriedMap(_, square, _)(_, null)([1, 2, 3]);
// all [1, 4, 9]
Note that I have to pass null every time, because _.map takes an optional third argument that lets you bind the callback to a context. This eager style of currying forces you to pass a fixed number of arguments. In the Variation section below, I'll show how this can be avoided with a lazy variant of curry.
The Ramda library omits the optional context parameter instead, so you need to pass exactly two instead of exactly three arguments to R.map. We can write a function that composes dataLast and curry and that optionally adjusts the arity, in order to make an Underscore function behave exactly like its Ramda counterpart:
const ramdaLike = (f, arity = f.length) => curry(dataLast(f), arity);
const ramdaMap = ramdaLike(_.map, 2);
ramdaMap(square, [1, 2, 3]);
ramdaMap(square)([1, 2, 3]);
ramdaMap(_, [1, 2, 3])(square);
// all [1, 4, 9]
Mapping this over the entire library requires some administration in order to get a satisfying result, but the result is a surprisingly faithful imitation of Ramda:
const arityOverrides = {
map: 2,
filter: 2,
reduce: 3,
extend: 2,
defaults: 2,
// etcetera, as desired
};
const R_ = _.extend(
// start with just passing everything through `ramdaLike`
_.mapObject(_, f => ramdaLike(f)),
// then replace a subset with arity overrides
_.mapObject(arityOverrides, (arity, name) => ramdaLike(_[name], arity)),
);
R_.identity(1); // 1
R_.map(square)([1, 2, 3]); // [1, 4, 9]
R_.filter(isOdd)([1, 2, 3]); // [1, 3]
const add = (a, b) => a + b;
const sum = R_.reduce(add, 0);
sum([1, 2, 3]); // 6
Variation
At the cost of introducing laziness, we can avoid having to fix the arity of a function. This lets us preserve all the optional and variadic parameters from the original Underscore functions, without always needing to supply them, and removes the need for per-function administration when mapping the library. We start with a variant of curry that returns a lazy function instead of an eager one:
function curryLazy(f, preArgs = [], thisArg) {
if (!_.isUndefined(thisArg)) f = f.bind(thisArg);
const applied = _.partial.apply(null, [f].concat(preArgs));
return _.restArguments(function(args) {
if (args.length > 0) {
return curryLazy(applied, args, this);
} else {
return applied.call(this);
}
});
}
This is basically R.curry with a builtin R.thunkify on top. Note that this implementation is actually a bit simpler than the eager variant. On top of that, creating a lazy, Ramda-like port of Underscore is reduced to an elegant oneliner:
const LR_ = _.mapObject(_, _.compose(curryLazy, dataLast));
We can now pass as many or as few arguments to each function as we want. We just have to append an extra call without arguments in order to force evaluation:
LR_.identity(1)(); // 1
LR_.map([1, 2, 3])(); // [1, 2, 3]
LR_.map(square)([1, 2, 3])(); // [1, 4, 9]
LR_.map(_, [1, 2, 3])(square)(); // [1, 4, 9]
LR_.map(Math.sqrt)(Math)([1, 4, 9])(); // [1, 2, 3]
LR_.filter([1, false, , '', 'yes'])(); // [1, 'yes']
LR_.filter(isOdd)([1, 2, 3])(); // [1, 3]
LR_.filter(_, [1, 2, 3])(isOdd)(); // [1, 3]
LR_.filter(window.confirm)(window)([1, 2, 3])(); // depends on user
LR_.extend({a: 1})({a: 2, b: 3})();
// {a: 1, b: 3}
LR_.extend({a: 1})({a: 2, b: 3})({a: 4})({b: 5, c: 6})();
// {a: 4, b: 3, c: 6}
This trades some faithfulness to Ramda for faithfulness to Underscore. In my opinion, it is a best of both worlds: data-last currying like in Ramda, with all the parametric flexibility from Underscore.
References:
Underscore documentation
Ramda documentation
If I want to define a function that filters a set matching on a given value, I can write:
const { filter, equals} = R
const data = [1, 2, 3, 4, 5]
const filterDataFor = x => filter(equals(x), data);
console.log(filterDataFor(2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
I thought that I might also be able to write this in the form of:
const { filter, equals, __ } = R
const data = [1, 2, 3, 4, 5]
const filterDataFor = filter(equals(__), data)
console.log(filterDataFor(2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
But evidently not; in the second example, filterDataFor is not a function. Could someone point out where I'm going wrong with this?
What you can do is to put a simple placeholder instead of equals(__) inside filter, then simply compose them together with equals:
const { filter, equals, __, compose } = R
const data = [1, 2, 3, 4, 5]
const filterDataFor = compose(filter(__, data), equals);
console.log(filterDataFor(2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Since compose composes the functions from right to left, when you call filterDataFor(2) it will first evaluate equals(2) then the resulting function will be applied instead of the placeholder, essentially resulting in filter(equals(2), data).
A variation on Andras Simon's answer is to flip the filter function, so that the predicate would be the 2nd parameter.
const { filter, equals, flip, compose } = R
const data = [1, 2, 3, 4, 5]
const filterDataFor = compose(flip(filter)(data), equals);
console.log(filterDataFor(2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Have you considered using without?
Returns a new list without values in the first argument. R.equals is used to determine equality.
without([2], [1, 2, 3, 4, 5]);
//=> [1, 3, 4, 5]
It works with multiple values as well:
without([2, 5], [1, 2, 3, 4, 5]);
//=> [1, 3, 4]
I am reading ramda documentation
const madd3 = R.lift((a, b, c) => a + b + c);
madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
It looks like a really useful function. I can't see what would be a use case for it.
Thanks
This function can only accept numbers:
const add3 = (a, b, c) => a + b + c;
add3(1, 2, 3); //=> 6
However what if these numbers were each contained in a functor? (i.e. a thing that contains a value; an array in the example below)
add3([1], [2], [3]); //=> "123"
That's obviously not what we want.
You can "lift" the function so that it can "extract" the value of each parameter/functor:
const add3Lifted = lift(add3);
add3Lifted([1], [2], [3]); //=> [6]
Arrays can obviously hold more than one value and combined with a lifted function that knows how to extract the values of each functor, you can now do this:
add3Lifted([1, 10], [2, 20], [3, 30]);
//=> [6, 33, 24, 51, 15, 42, 33, 60]
Which is basically what you'd have got if you had done this:
[
add3(1, 2, 3), // 6
add3(1, 2, 30), // 33
add3(1, 20, 3), // 24
add3(1, 20, 30), // 51
add3(10, 2, 3), // 15
add3(10, 2, 30), // 42
add3(10, 20, 3), // 33
add3(10, 20, 30) // 60
]
Note that each array doesn't have to be of the same length:
add3Lifted([1, 10], [2], [3]);
//=> [6, 15]
So to answer your question: if you intend to run a function with different sets of values, lifting that function may be a useful thing to consider:
const results = [add3(1, 2, 3), add3(10, 2, 3)];
is the same as:
const results = add3Lifted([1, 10], [2], [3]);
Functional programming is a long and mathematical topic, in particular the part dealing with monads and cathegory theory in general. But it is worth to take a look at it, here is a funny introduction with pictures.
In short, lift is a function that will take a n-arguments function and will produce a function that takes n wrapped-values and produces another resulting wrapped-value. A lift that take a one-argument function is defined by the following type signature
// name :: f is a wrp-value => function -> wrp-value -> wrp-value
liftA :: Applicative f => (a -> b) -> f a -> f b
Wait... Wrapped-value?
I will introduce briefly Haskell, only to explain this. In haskell, an easy example of wrapped-value is Maybe, Maybe can be a wrapped-value or nothing, that is also a wrapped-value. The following example applies a function to a Maybe containing a value, and a empty Maybe.
> liftA (+ 8) (Just 8)
Just 16
> liftA (+ 8) Nothing
Nothing
The list is also a wrapped-value, and we can apply functions to it. In the second case liftA2 applies two-argument functions to two lists.
> liftA (+ 8) [1,2,3]
[9,10,11]
> liftA2 (*) [1..3] [1..3]
[1,2,3,2,4,6,3,6,9]
This wrapped-value is an Applicative Functor, so from now I will call it Applicative.
Maybe Maybe you are starting to lose interest from this point...
But someone before us has got lost on this topic, finally he survived and published it as an answer to this question.
Lets look at what did he see...
...
He saw Fantasy Land
In fantasy-land, an object implements Apply spec when it has
an ap method defined (that object also has to implement
Functor spec by defining a map method).
Fantasy-land is a fancy name to a functional programming spec in
javascript. Ramda follows it.
Apply is our Applicative, a
Functor that implements also an ap method.
A Functor, is something that has the map method.
So, wait... the Array in javascript has a map...
[1,2,3].map((a)=>a+1) \\=> [ 2, 3, 4 ]
Then the Array is a Functor, and map applies a function to all values of it, returning another Functor with the same number of values.
But what does the ap do?
ap applies a list of functions to a list of values.
Dispatches to the ap method of the second argument, if present. Also
treats curried functions as applicatives.
Let's try to do something with it.
const res = R.ap(
[
(a)=>(-1*a),
(a)=>((a>1?'greater than 1':'a low value'))
],
[1,2,3]); //=> [ -1, -2, -3, "a low value", "greater than 1", "greater than 1" ]
console.log(res);
<script src="https://cdn.jsdelivr.net/npm/ramda#0.26.1/dist/ramda.min.js"></script>
The ap method takes an Array (or some other Applicative) of functions an applies it to a Applicative of values to produce another Applicative flattened.
The signature of the method explains this
[a → b] → [a] → [b]
Apply f => f (a → b) → f a → f b
Finally, what does lift do?
Lift takes a function with n arguments, and produces another function that takes n Aplicatives and produces a flattened Aplicative of the results.
In this case our Applicative is the Array.
const add2 = (a, b) => a + b;
const madd2 = R.lift(add2);
const res = madd2([1,2,3], [2,3,4]);
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
console.log(res);
// Equivalent to lift using ap
const result2 = R.ap(R.ap(
[R.curry(add2)], [1, 2, 3]),
[2, 3, 4]
);
//=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
console.log(result2);
<script src="https://cdn.jsdelivr.net/npm/ramda#0.26.1/dist/ramda.min.js"></script>
These wrappers (Applicatives, Functors, Monads) are interesting because they can be anything that implements these methods. In haskell, this is used to wrap unsafe operations, such as input/output. It can also be an error wrapper or a tree, even any data structure.
What hasn't been mentioned in the current answers is that functions like R.lift will not only work with arrays but any well behaved Apply1 data type.
For example, we can reuse the same function produced by R.lift:
const lifted = lift((a, b, c) => a + b - c)
With functions as the Apply type:
lifted(a => a * a,
b => b + 5,
c => c * 3)(4) //=> 13
Optional types (dispatching to .ap):
const Just = val => ({
map: f => Just(f(val)),
ap: other => other.map(otherVal => val(otherVal)),
getOr: _ => val
})
const Nothing = {
map: f => Nothing,
ap: other => Nothing,
getOr: x => x
}
lifted(Just(4), Just(6), Just(8)).getOr(NaN) //=> 2
lifted(Just(4), Nothing, Just(8)).getOr(NaN) //=> NaN
Asynchronous types (dispatching to .ap):
const Asynchronous = fn => ({
run: fn,
map: f => Asynchronous(g => fn(a => g(f(a)))),
ap: other => Asynchronous(fb => fn(f => other.run(a => fb(f(a)))))
})
const delay = (n, x) => Asynchronous(then => void(setTimeout(then, n, x)))
lifted(delay(2000, 4), delay(1000, 6), delay(500, 8)).run(console.log)
... and many more. The point here is that anything that can uphold the interface and laws expected of any Apply type can make use of generic functions such as R.lift.
1. The argument order of ap as listed in the fantasy-land spec is reversed from the order supported by name dispatching in Ramda, though is still supported when using the fantasy-land/ap namespaced method.
Basically it is taking a cartesian product and applies a function to each array.
const
cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []),
fn = ([a, b, c]) => a + b + c,
result = [[1, 2, 3], [1, 2, 3], [1]]
.reduce(cartesian)
.map(fn);
console.log(result); // [3, 4, 5, 4, 5, 6, 5, 6, 7]
I'm trying to get a better understanding of recursion as well as functional programming, I thought a good practice example for that would be to create permutations of a string with recursion and modern methods like reduce, filter and map.
I found this beautiful piece of code
const flatten = xs =>
xs.reduce((cum, next) => [...cum, ...next], []);
const without = (xs, x) =>
xs.filter(y => y !== x);
const permutations = xs =>
flatten(xs.map(x =>
xs.length < 2
? [xs]
: permutations(without(xs, x)).map(perm => [x, ...perm])
));
permutations([1,2,3])
// [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
from Permutations in JavaScript?
by Márton Sári
I've delimited it a bit in order to add some console logs to debug it and understand what's it doing behind the scenes
const flatten = xs => {
console.log(`input for flatten(${xs})`);
return xs.reduce((cum, next) => {
let res = [...cum, ...next];
console.log(`output from flatten(): ${res}`);
return res;
}, []);
}
const without = (xs, x) => {
console.log(`input for without(${xs},${x})`)
let res = xs.filter(y => y !== x);
console.log(`output from without: ${res}`);
return res;
}
const permutations = xs => {
console.log(`input for permutations(${xs})`);
let res = flatten(xs.map(x => {
if (xs.length < 2) {
return [xs]
} else {
return permutations(without(xs, x)).map(perm => [x, ...perm])
}
}));
console.log(`output for permutations: ${res}`)
return res;
}
permutations([1,2,3])
I think I have a good enough idea of what each method iss doing, but I just can't seem to conceptualize how it all comes together to create [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
can somebody show me step by step what's going on under the hood?
To get all permuations we do the following:
We take one element of the array from left to right.
xs.map(x => // 1
For all the other elements we generate permutations recursively:
permutations(without(xs, x)) // [[2, 3], [3, 2]]
for every permutation we add the value we've taken out back at the beginning:
.map(perm => [xs, ...perm]) // [[1, 2, 3], [1, 3, 2]]
now that is repeated for all the arrays elements and it results in:
[
// 1
[[1, 2, 3], [1, 3, 2]],
// 2
[[2, 1, 3], [2, 3, 1]],
// 3
[[3, 1, 2], [3, 2, 1]]
]
now we just have to flatten(...) that array to get the desired result.
The whole thing could be expressed as a tree of recursive calls:
[1, 2, 3]
- [2, 3] ->
- [3] -> [1, 2, 3]
- [2] -> [1, 3, 2]
- [1, 3] ->
- [1] -> [2, 3, 1]
- [3] -> [2, 1, 3]
- [1, 2] ->
- [1] -> [3, 2, 1]
- [2] -> [3, 1, 2]
I've delimited it a bit in order to add some console logs to debug it
This can help of course. However keep in mind that simple recursive definitions can often result in complex execution traces.
That is in fact one of reasons why recursion can be so useful. Because some algorithms that have complicated iterations, admit a simple recursive description. So your goal in understanding a recursive algorithm should be to figure out the inductive (not iterative) reasoning in its definition.
Lets forget about javascript and focus on the algorithm. Let's see we can obtain the permutations of elements of a set A, which we will denote P(A).
Note: It's of no relevance that in the original algorithm the input is a list, since the original order does not matter at all. Likewise it's of no relevance that we will return a set of lists rather than a list of lists, since we don't care the order in which solutions are calculated.
Base Case:
The simplest case is the empty set. There is exactly one solution for the permutations of 0 elements, and that solution is the empty sequence []. So,
P(A) = {[]}
Recursive Case:
In order to use recursion, you want to describe how to obtain P(A) from P(A') for some A' smaller than A in size.
Note: If you do that, it's finished. Operationally the program will work out via successive calls to P with smaller and smaller arguments until it reaches the base case, and then it will come back bulding longer results from shorter ones.
So here is one way to write a particular permutation of an A with n+1 elems. You need to successively pick one element of A for each position:
_ _ ... _
n+1 n 1
So you pick an x ∈ A for the first
x _ ... _
n 1
And then you need to choose a permutation in P(A\{x}).
This tells you one way to build all permutations of size n. Consider all possible choices of x in A (to use as first element), and for each choice put x in front of each solution of P(A\{x}). Finally take the union of all solutions you found for each choice of x.
Let's use the dot operator to represent putting x in front of a sequence s, and the diamond operator to represent putting x in front of every s ∈ S. That is,
x⋅s = [x, s1, s2, ..., sn]
x⟡S = {x⋅s : s ∈ S}
Then for a non-empty A
P(A) = ⋃ {x⟡P(A\{x}) : x ∈ A}
This expression together with the case base give you all the permutations of elements in a set A.
The javascript code
To understand how the code you've shown implements this algortithm you need to consider the following
That code considers two base cases, when you have 0 or 1 elements, by writing xs.length < 2. We could have done that too, it's irrelevant. You can change that 2 into a 1 and it should still work.
The mapping corresponds to our operation x⟡S = {x⋅s : s ∈ S}
The without corresponds to P(A\{x})
The flatten corresponds to the ⋃ which joins all solutions.
I want to take an array [1, 2, 3] and return [1, 2, 3, 1].
I'm using Ramda, and I can get the desired result like this:
const fn = arr => R.append(R.prop(0, arr), arr);
But I'd like to do it point-free. Here's the closest I've gotten:
const fn = R.compose(R.append, R.prop(0));
fn(arr)(arr)
But that looks silly. What am I missing? Thanks!
converge can be very helpful for things like this.
const rotate = R.converge(R.append, [R.head, R.identity])
rotate([1, 2, 3]); //=> [1, 2, 3, 1]
The S combinator is useful here:
S.S(S.C(R.append), R.head, [1, 2, 3]);
// => [1, 2, 3, 1]