Can't wrap my head around "lift" in Ramda.js - javascript

Looking at the source for Ramda.js, specifically at the "lift" function.
lift
liftN
Here's the given example:
var madd3 = R.lift(R.curry((a, b, c) => a + b + c));
madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
So the first number of the result is easy, a, b, and c, are all the first elements of each array. The second one isn't as easy for me to understand. Are the arguments the second value of each array (2, 2, undefined) or is it the second value of the first array and the first values of the second and third array?
Even disregarding the order of what's happening here, I don't really see the value. If I execute this without lifting it first I will end up with the arrays concatenated as strings. This appears to sort of be working like flatMap but I can't seem to follow the logic behind it.

Bergi's answer is great. But another way to think about this is to get a little more specific. Ramda really needs to include a non-list example in its documentation, as lists don't really capture this.
Lets take a simple function:
var add3 = (a, b, c) => a + b + c;
This operates on three numbers. But what if you had containers holding numbers? Perhaps we have Maybes. We can't simply add them together:
const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!
(Ok, this is Javascript, it will not actually throw an error here, just try to concatenate thing it shouldn't... but it definitely doesn't do what you want!)
If we could lift that function up to the level of containers, it would make our life easier. What Bergi pointed out as lift3 is implemented in Ramda with liftN(3, fn), and a gloss, lift(fn) that simply uses the arity of the function supplied. So, we can do:
const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()
But this lifted function doesn't know anything specific about our containers, only that they implement ap. Ramda implements ap for lists in a way similar to applying the function to the tuples in the crossproduct of the lists, so we can also do this:
madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]
That is how I think about lift. It takes a function that works at the level of some values and lifts it up to a function that works at the level of containers of those values.

Thanks to the answers from Scott Sauyet and Bergi, I wrapped my head around it. In doing so, I felt there were still hoops to jump to put all the pieces together. I will document some questions I had in the journey, hope it could be of help to some.
Here's the example of R.lift we try to understand:
var 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]
To me, there are three questions to be answered before understanding it.
Fantasy-land's Apply spec (I will refer to it as Apply) and what Apply#ap does
Ramda's R.ap implementation and what does Array has to do with the Apply spec
What role does currying play in R.lift
Understanding the Apply spec
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).
The ap method has the following signature:
ap :: Apply f => f a ~> f (a -> b) -> f b
In fantasy-land's type signature notation:
=> declares type constraints, so f in the signature above refers to type Apply
~> declares method declaration, so ap should be a function declared on Apply which wraps around a value which we refer to as a (we will see in the example below, some fantasy-land's implementations of ap are not consistent with this signature, but the idea is the same)
Let's say we have two objects v and u (v = f a; u = f (a -> b)) thus this expression is valid v.ap(u), some things to notice here:
v and u both implement Apply. v holds a value, u holds a function but they have the same 'interface' of Apply (this will help in understanding the next section below, when it comes to R.ap and Array)
The value a and function a -> b are ignorant of Apply, the function just transforms the value a. It's the Apply that puts value and function inside the container and ap that extracts them out, invokes the function on the value and puts them back in.
Understanding Ramda's R.ap
The signature of R.ap has two cases:
Apply f => f (a → b) → f a → f b: This is very similar to the signature of Apply#ap in last section, the difference is how ap is invoked (Apply#ap vs. R.ap) and the order of params.
[a → b] → [a] → [b]: This is the version if we replace Apply f with Array, remember that the value and function has to be wrapped in the same container in the previous section? That's why when using R.ap with Arrays, the first argument is a list of functions, even if you want to apply only one function, put it in an Array.
Let's look at one example, I'm using Maybe from ramda-fantasy, which implements Apply, one inconsistency here is that Maybe#ap's signature is: ap :: Apply f => f (a -> b) ~> f a -> f b. Seems some other fantasy-land implementations also follow this, however, it shouldn't affect our understanding:
const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;
const a = Maybe.of(2);
const plus3 = Maybe.of(x => x + 3);
const b = plus3.ap(a); // invoke Apply#ap
const b2 = R.ap(plus3, a); // invoke R.ap
console.log(b); // Just { value: 5 }
console.log(b2); // Just { value: 5 }
Understanding the example of R.lift
In R.lift's example with arrays, a function with arity of 3 is passed to R.lift: var madd3 = R.lift((a, b, c) => a + b + c);, how does it work with the three arrays [1, 2, 3], [1, 2, 3], [1]? Also note that it's not curried.
Actually inside source code of R.liftN (which R.lift delegates to), the function passed in is auto-curried, then it iterates through the values (in our case, three arrays), reducing to a result: in each iteration it invokes ap with the curried function and one value (in our case, one array). It's hard to explain in words, let's see the equivalent in code:
const R = require('ramda');
const madd3 = (x, y, z) => x + y + z;
// example from R.lift
const result = R.lift(madd3)([1, 2, 3], [1, 2, 3], [1]);
// this is equivalent of the calculation of 'result' above,
// R.liftN uses reduce, but the idea is the same
const result2 = R.ap(R.ap(R.ap([R.curry(madd3)], [1, 2, 3]), [1, 2, 3]), [1]);
console.log(result); // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
console.log(result2); // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
Once the expression of calculating result2 is understood, the example will become clear.
Here's another example, using R.lift on Apply:
const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;
const madd3 = (x, y, z) => x + y + z;
const madd3Curried = Maybe.of(R.curry(madd3));
const a = Maybe.of(1);
const b = Maybe.of(2);
const c = Maybe.of(3);
const sumResult = madd3Curried.ap(a).ap(b).ap(c); // invoke #ap on Apply
const sumResult2 = R.ap(R.ap(R.ap(madd3Curried, a), b), c); // invoke R.ap
const sumResult3 = R.lift(madd3)(a, b, c); // invoke R.lift, madd3 is auto-curried
console.log(sumResult); // Just { value: 6 }
console.log(sumResult2); // Just { value: 6 }
console.log(sumResult3); // Just { value: 6 }
A better example suggested by Scott Sauyet in the comments (he provides quite some insights, I suggest you read them) would be easier to understand, at least it points the reader to the direction that R.lift calculates the Cartesian product for Arrays.
var madd3 = R.lift((a, b, c) => a + b + c);
madd3([100, 200], [30, 40, 50], [6, 7]); //=> [136, 137, 146, 147, 156, 157, 236, 237, 246, 247, 256, 257]
Hope this helps.

lift/liftN "lifts" an ordinary function into an Applicative context.
// lift1 :: (a -> b) -> f a -> f b
// lift1 :: (a -> b) -> [a] -> [b]
function lift1(fn) {
return function(a_x) {
return R.ap([fn], a_x);
}
}
Now the type of ap (f (a->b) -> f a -> f b) isn't easy to understand either, but the list example should be understandable.
The interesting thing here is that you pass in a list and get back a list, so you can repeatedly apply this as long as the function(s) in the first list have the correct type:
// lift2 :: (a -> b -> c) -> f a -> f b -> f c
// lift2 :: (a -> b -> c) -> [a] -> [b] -> [c]
function lift2(fn) {
return function(a_x, a_y) {
return R.ap(R.ap([fn], a_x), a_y);
}
}
And lift3, which you implicitly used in your example, works the same - now with ap(ap(ap([fn], a_x), a_y), a_z).

Related

JavaScript destructuring with an array element

I have met the following questions in JavaScript:
const [x1, ...[result]] = [3, 4, 5]
console.log([result])
I know x1 is 3, but why is the logging result [4] instead of [4,5]?
So basically what is happening if we follow this syntax
const [a,...b] = [3,4,5]
Javascript creates an array called b and has the value [4,5]
But in your case what is happening is,
const [a,...[b]] = [3,4,5]
This is essentially assigning to only the first variable of the empty array with first value as b, which always equals 4 and not [4,5] as you expect.
So it's equivalent to the case below
const [a,...[b,c]] = [3,4,5]
the only difference is that you are not providing a variable c in your case.
So b would correspond to 4 and c would correspond to 5

Weird function syntax

I saw a weird function that looked something like:
const x = (a) => (b) => a + b;
console.log(x(1)(2))
The output is 3, I understand that it's a function returning a function and both a and b are in the same scope but the questions I have are:
How could this be used in real life?
What's the advantage of not using a function with 2 parameters and using this instead (for a one-line function)?
With this closure, you could get a function with a constant value for later adding.
How could this be used in real life?
You could take the returned function for a mapping of an array.
What's the advantage of not using a function with 2 parameters and using this instead (for a one-line function)?
It's a cleaner and functional approach.
const
x = a => b => a + b,
add5 = x(5);
console.log([1, 2, 3].map(add5));
Let's give that function a better name:
const add = (a) => (b) => a + b
Then later you can write
[1, 2, 3, 4] .map (add (5)) //=> [6, 7, 8, 9]
which is nicer to read than
[1, 2, 3, 4] .map ((n) => 5 + n) //=> [6, 7, 8, 9]
This is handy in a chain of .then() calls on Promises:
return fetchList (param)
.then (map (add (5)))
.then (filter (lessThan (8)))
.then (average)
(This of course requires curried functions add, lessThan, map, and filter, and some simple average function.)
Compare this to
return fetchList (param)
.then (xs => xs.map (x => add (5, x)))
.then (xs => xs.filter (x => lessThan (8, x)))
.then (average)
Note that the reason that average works the same in both versions of this is that it
takes a single parameter. One major point of currying is to turn a function into one that takes a single parameter. It makes a certain style of coding much easier to perform.
Nina gave an excellent answer. I will provide another, a little more advanced example where such closures help a lot with the clarity of the code. Let's combine functions together into a prefix-checker as below and then re-use it as many times as we want:
//given a word, check if a string s starts with this word
const literal = word => s => s && s.startsWith(word);
//allow to combine 2 literals with OR
const either = (p1, p2) => s => p1(s) || p2(s);
//allow to combine N literals
const any = (...parsers) => parsers.reduce(either);
//create a parser
const check = any(literal('cat'),literal('dog'),literal('cow'));
console.log('cat: ' + check('cat'));
console.log('dog: ' + check('dog is smart'));
console.log('cow: ' + check('cow 123'));
console.log('banana: ' + check('banana'));
In reality, it is a simplified parser-combinator (nope, not yet monadic). Extending this approach, you can create parsers for your own programming language, and it would be maintainable and fast.

Sorting array multiple times and storing in an object [duplicate]

Let's suppose I wanted a sort function that returns a sorted copy of the inputted array. I naively tried this
function sort(arr) {
return arr.sort();
}
and I tested it with this, which shows that my sort method is mutating the array.
var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a); //alerts "1,2,3,3,3,4,5,7,7"
I also tried this approach
function sort(arr) {
return Array.prototype.sort(arr);
}
but it doesn't work at all.
Is there a straightforward way around this, preferably a way that doesn't require hand-rolling my own sorting algorithm or copying every element of the array into a new one?
You need to copy the array before you sort it. One way with es6:
const sorted = [...arr].sort();
The spread-syntax as array literal (copied from mdn):
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
Just copy the array. There are many ways to do that:
function sort(arr) {
return arr.concat().sort();
}
// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects
Try the following
function sortCopy(arr) {
return arr.slice(0).sort();
}
The slice(0) expression creates a copy of the array starting at element 0.
You can use slice with no arguments to copy an array:
var foo,
bar;
foo = [3,1,2];
bar = foo.slice().sort();
You can also do this
d = [20, 30, 10]
e = Array.from(d)
e.sort()
This way d will not get mutated.
function sorted(arr) {
temp = Array.from(arr)
return temp.sort()
}
//Use it like this
x = [20, 10, 100]
console.log(sorted(x))
Update - Array.prototype.toSorted() proposal
The Array.prototype.toSorted(compareFn) -> Array is a new method which was proposed to be added to the Array.prototype and is currently in stage 3 (Soon to be available).
This method will keep the target Array untouched and returns a copy of it with the change performed instead.
Anyone who wants to do a deep copy (e.g. if your array contains objects) can use:
let arrCopy = JSON.parse(JSON.stringify(arr))
Then you can sort arrCopy without changing arr.
arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)
Please note: this can be slow for very large arrays.
Try this to sort the numbers. This does not mutate the original array.
function sort(arr) {
return arr.slice(0).sort((a,b) => a-b);
}
There's a new tc39 proposal, which adds a toSorted method to Array that returns a copy of the array and doesn't modify the original.
For example:
const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]
As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js.
I think that my answer is a bit too late but if someone come across this issue again the solution may be useful.
I can propose yet another approach with a native function which returns a sorted array.
This code still mutates the original object but instead of native behaviour this implementation returns a sorted array.
// Remember that it is not recommended to extend build-in prototypes
// or even worse override native functions.
// You can create a seperate function if you like
// You can specify any name instead of "sorted" (Python-like)
// Check for existence of the method in prototype
if (typeof Array.prototype.sorted == "undefined") {
// If it does not exist you provide your own method
Array.prototype.sorted = function () {
Array.prototype.sort.apply(this, arguments);
return this;
};
}
This way of solving the problem was ideal in my situation.
You can also extend the existing Array functionality. This allows chaining different array functions together.
Array.prototype.sorted = function (compareFn) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]
Same in typescript:
// extensions.ts
Array.prototype.sorted = function (compareFn?: ((a: any, b: any) => number) | undefined) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
declare global {
interface Array<T> {
sorted(compareFn?: (a: T, b: T) => number): Array<T>;
}
}
export {}
// index.ts
import 'extensions.ts';
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]

Difference between curry and curryRight in Lodash

What is the difference between curry and curryRight in Lodash?
Is just the application order of the provided arguments switched from f(a,b,c) which applies to f(a) -> f(b) -> f(c) to f(a,b,c) which then applies to f(c) -> f(b) -> f(a)?
I already looked into the Lodash documentation but that didn‘t helped me.
From the documentation:
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curryRight(abc);
curried(3)(2)(1);
// => [1, 2, 3]
curried(2, 3)(1);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
The first example is simple. The order of the arguments is reversed (in comparison to _.curry).
The second and third one can perhaps be confusing.
In the third example we see that the order of the arguments is NOT reversed. That is because only the currying is applied in reverse. In other words the parentheses are applied in the reverse order, but what is inside of the parentheses sustains the original order.
Compare this to the result of _.curry(_.flip(f)):
var abc = function(a, b, c) {
  return [a, b, c];
};
var curried = _.curry(_.flip(abc), 3);
 
curried(3)(2)(1);
// => [1, 2, 3]
curried(3, 2)(1);
// => [1, 2, 3]
curried(3, 2, 1);
// => [1, 2, 3]
As you can see the result is different. Now the order of the arguments is reversed totally in all the examples.
Btw notice that for some reason I needed to specify the arity to 3 in _.curry(_.flip(abc), 3);. I don't know why but it causes an exception without that.

How can you sort an array without mutating the original array?

Let's suppose I wanted a sort function that returns a sorted copy of the inputted array. I naively tried this
function sort(arr) {
return arr.sort();
}
and I tested it with this, which shows that my sort method is mutating the array.
var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a); //alerts "1,2,3,3,3,4,5,7,7"
I also tried this approach
function sort(arr) {
return Array.prototype.sort(arr);
}
but it doesn't work at all.
Is there a straightforward way around this, preferably a way that doesn't require hand-rolling my own sorting algorithm or copying every element of the array into a new one?
You need to copy the array before you sort it. One way with es6:
const sorted = [...arr].sort();
The spread-syntax as array literal (copied from mdn):
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
Just copy the array. There are many ways to do that:
function sort(arr) {
return arr.concat().sort();
}
// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects
Try the following
function sortCopy(arr) {
return arr.slice(0).sort();
}
The slice(0) expression creates a copy of the array starting at element 0.
You can use slice with no arguments to copy an array:
var foo,
bar;
foo = [3,1,2];
bar = foo.slice().sort();
You can also do this
d = [20, 30, 10]
e = Array.from(d)
e.sort()
This way d will not get mutated.
function sorted(arr) {
temp = Array.from(arr)
return temp.sort()
}
//Use it like this
x = [20, 10, 100]
console.log(sorted(x))
Update - Array.prototype.toSorted() proposal
The Array.prototype.toSorted(compareFn) -> Array is a new method which was proposed to be added to the Array.prototype and is currently in stage 3 (Soon to be available).
This method will keep the target Array untouched and returns a copy of it with the change performed instead.
Anyone who wants to do a deep copy (e.g. if your array contains objects) can use:
let arrCopy = JSON.parse(JSON.stringify(arr))
Then you can sort arrCopy without changing arr.
arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)
Please note: this can be slow for very large arrays.
Try this to sort the numbers. This does not mutate the original array.
function sort(arr) {
return arr.slice(0).sort((a,b) => a-b);
}
There's a new tc39 proposal, which adds a toSorted method to Array that returns a copy of the array and doesn't modify the original.
For example:
const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]
As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js.
I think that my answer is a bit too late but if someone come across this issue again the solution may be useful.
I can propose yet another approach with a native function which returns a sorted array.
This code still mutates the original object but instead of native behaviour this implementation returns a sorted array.
// Remember that it is not recommended to extend build-in prototypes
// or even worse override native functions.
// You can create a seperate function if you like
// You can specify any name instead of "sorted" (Python-like)
// Check for existence of the method in prototype
if (typeof Array.prototype.sorted == "undefined") {
// If it does not exist you provide your own method
Array.prototype.sorted = function () {
Array.prototype.sort.apply(this, arguments);
return this;
};
}
This way of solving the problem was ideal in my situation.
You can also extend the existing Array functionality. This allows chaining different array functions together.
Array.prototype.sorted = function (compareFn) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]
Same in typescript:
// extensions.ts
Array.prototype.sorted = function (compareFn?: ((a: any, b: any) => number) | undefined) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
declare global {
interface Array<T> {
sorted(compareFn?: (a: T, b: T) => number): Array<T>;
}
}
export {}
// index.ts
import 'extensions.ts';
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]

Categories

Resources