How to compose functions and then apply arguments in Lodash/FP - javascript

I am trying to learn more about using currying and composition in functional programming by using Lodash/FP to clean up some old code. However, I am repeatedly running into situations where I have a function and I want to pass it one or more functions. I then want to pass the values that will be used as the arguments to the functions that I passed to the original function.
I'm finding it difficult to explain exactly what I'm trying to do so I made a JS Fiddle that shows how I have been trying to approach this:
https://jsfiddle.net/harimau777/rqkLf1rg/2/
const foo = a => `${a}${a}`
// Desired Behavior: const ans1 = (a, b) => `${foo(a)}${foo(b)}`
const ans1 = _.compose(
(a, b) => `${a}${b}`,
foo,
foo
)
// Desired Result: '1122'
console.log(ans1('1', '2'))
// Desired Behavior: const ans2 = a => a.map(a => a + 1)
const ans2 = _.compose(
_.map,
a => a + 1
)
//Desired Result: [2, 3, 4]
console.log(ans2([1, 2, 3]))
Based on Ori Drori's answer below I think that I can clarify my question (is this how people normally follow up on StackOverflow as opposed to asking a new question?):
Suppose that instead of applying the same sequence of functions to both inputs I wanted to apply a sequence of functions to the first input, a different sequence to the second input, and use both results as the input to the rest of the _.compose. I could do this using:
const f1 = _.compose(<Some sequence of functions>)
const f2 = _.compose(<Some sequence of functions>)
const f3 = <A function which takes two inputs>
const ans = _.compose(
<More functions here>,
f3
)(f1(a), f2(b))
console.log(ans)
However, I'm wondering if there is a way to handle this using a single compose or if there are any patterns that tend to be used in functional programming to handle situations like this.

LodashFPs _.compose() (_.flowRight() in lodash) works by applying the parameters to the right most (bottom in your code) function, and passes the result to the function to it's left, and so on:
_.compose(a, b, c)(params) -> a(b(c(params)))
This means that every function, except for the right most receives only one parameter.
You can get the 1st example working by changing the methods a bit:
const ans1 = _.compose(
arr => arr.join(''), // joins the params to one string
(...args) => args.map((s) => `${s}${s}`) // returns an array of double params
)
// Desired Result: '1122'
console.log(ans1('1', '2'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js"></script>
In the 2nd example you want to create a new method that maps via a predefined callback. Since lodash/fp methods are auto curried, you can supply the callback to the _.map(), and get a new method. Compose won't work here since _.map() doesn't use the results of the method directly, but applies it to every item in the array:
const ans2 = _.map(a => a + 1)
//Desired Result: [2, 3, 4]
console.log(ans2([1, 2, 3]))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js"></script>
The case you've presented in your clarification can be handled by _.useWith() (known as _.overArgs() in lodash):
Creates a function that invokes func with its arguments transformed.
However, it's use is not recommended since it reduces readability.
Example:
const foo = a => `${a}${a}`
const ans1 = _.useWith(
(a, b) => `${a}${b}`,
[
foo,
foo
]
)
const result = ans1(1, 2)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

Related

Why do i get just a number from console.log(Array.push()); in Javascript? [duplicate]

Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
I don't know if this has already been proposed or asked before; Google searches returned only a myriad number of questions related to the current functionality of Array.push().
Here's an example implementation of this functionality, feel free to correct it:
;(function() {
var _push = Array.prototype.push;
Array.prototype.push = function() {
return this[_push.apply(this, arguments) - 1];
}
}());
You would then be able to do something like this:
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray.push({}));
Where someFunction modifies the object passed in as the second parameter, for example. Now the contents of someArray are [{"someKey": "hello world"}].
Are there any drawbacks to this approach?
See my detailed answer here
TLDR;
You can get the return value of the mutated array, when you instead add an element using array.concat[].
concat is a way of "adding" or "joining" two arrays together. The awesome thing about this method, is that it has a return value of the resultant array, so it can be chained.
newArray = oldArray.concat[newItem];
This also allows you to chain functions together
updatedArray = oldArray.filter((item) => {
item.id !== updatedItem.id).concat[updatedItem]};
Where item = {id: someID, value: someUpdatedValue}
The main thing to notice is, that you need to pass an array to concat.
So make sure that you put your value to be "pushed" inside a couple of square brackets, and you're good to go.
This will give you the functionality you expected from push()
You can use the + operator to "add" two arrays together, or by passing the arrays to join as parameters to concat().
let arrayAB = arrayA + arrayB;
let arrayCD = concat(arrayC, arrayD);
Note that by using the concat method, you can take advantage of "chaining" commands before and after concat.
Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
Of course there is one: Other code will expect Array::push to behave as defined in the specification, i.e. to return the new length. And other developers will find your code incomprehensible if you did redefine builtin functions to behave unexpectedly.
At least choose a different name for the method.
You would then be able to do something like this: someFunction(value, someArray.push({}));
Uh, what? Yeah, my second point already strikes :-)
However, even if you didn't use push this does not get across what you want to do. The composition that you should express is "add an object which consist of a key and a value to an array". With a more functional style, let someFunction return this object, and you can write
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
return obj;
}
someArray.push(someFunction(value, {}));
Just as a historical note -- There was an older version of JavaScript -- JavaScript version 1.2 -- that handled a number of array functions quite differently.
In particular to this question, Array.push did return the item, not the length of the array.
That said, 1.2 has been not been used for decades now -- but some very old references might still refer to this behavior.
http://web.archive.org/web/20010408055419/developer.netscape.com/docs/manuals/communicator/jsguide/js1_2.htm
By the coming of ES6, it is recommended to extend array class in the proper way , then , override push method :
class XArray extends Array {
push() {
super.push(...arguments);
return (arguments.length === 1) ? arguments[0] : arguments;
}
}
//---- Application
let list = [1, 3, 7,5];
list = new XArray(...list);
console.log(
'Push one item : ',list.push(4)
);
console.log(
'Push multi-items :', list.push(-9, 2)
);
console.log(
'Check length :' , list.length
)
Method push() returns the last element added, which makes it very inconvenient when creating short functions/reducers. Also, push() - is a rather archaic stuff in JS. On ahother hand we have spread operator [...] which is faster and does what you needs: it exactly returns an array.
// to concat arrays
const a = [1,2,3];
const b = [...a, 4, 5];
console.log(b) // [1, 2, 3, 4, 5];
// to concat and get a length
const arrA = [1,2,3,4,5];
const arrB = [6,7,8];
console.log([0, ...arrA, ...arrB, 9].length); // 10
// to reduce
const arr = ["red", "green", "blue"];
const liArr = arr.reduce( (acc,cur) => [...acc, `<li style='color:${cur}'>${cur}</li>`],[]);
console.log(liArr);
//[ "<li style='color:red'>red</li>",
//"<li style='color:green'>green</li>",
//"<li style='color:blue'>blue</li>" ]
var arr = [];
var element = Math.random();
assert(element === arr[arr.push(element)-1]);
How about doing someArray[someArray.length]={} instead of someArray.push({})? The value of an assignment is the value being assigned.
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray[someArray.length]={});
console.log(someArray)

In Javascript, is there an easyish way to get a chainable Array prepend operation like the reverse of Array.concat?

I'm doing array manipulation in Javascript, and I want to be able to chain operations with multiple calls to map, concat, etc.
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique)
But the problem I've run into is that Array.precat doesn't exist. (Think of Array.concat, but the reverse.)
I don't want to modify Array.prototype in my own code, for reasons. (https://flaviocopes.com/javascript-why-not-modify-object-prototype/)
I could totally use Array.concat and concatenate my array to the end of the prefix array and carry on. But that doesn't chain with the other stuff, and it makes my code look clunky.
It's kind of a minor issue because I can easily write code to get the output I want. But it's kind of a big deal because I want my code to look clean and this seems like a missing piece of the Array prototype.
Is there a way to get what I want without modifying the prototype of a built-in type?
For more about the hypothetical Array.precat, see also:
concat, but prepend instead of append
You could use Array#reduce with a function which takes the initialValue as array for prepending data.
const
precat = (a, b) => [...a, b],
result = [1, 2, 3]
.reduce(precat, [9, 8, 7]);
console.log(result)
If you don't want to modify Array.prototype, you can consider extends:
class AmazingArray extends Array {
precat(...args) {
return new AmazingArray().concat(...args, this);
}
}
const transformStuff = x => 2*x;
const myAwesomeSortAlgorithm = (a, b) => a - b;
const someSuffixElement = 19;
const newFirstElement = -1;
const unique = (x, i, arr) => arr.indexOf(x) === i;
const someAmazingArrayOperation = (list) =>
new AmazingArray()
.concat(list)
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique);
console.log(someAmazingArrayOperation([9, 2, 2, 3]));
I don't want to modify Array.prototype in my own code, for reasons.
These reasons are good, but you can sidestep them by using a collision-safe property - key it with a symbol, not a name:
const precat = Symbol('precatenate')
Array.prototype[precat] = function(...args) {
return [].concat(...args, this);
};
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeCompareFunction)
.concat([someSuffixElement])
[precat]([newFirstElement])
.filter(unique);

Is there a way to check whether the argument passed into a function is an array in javascript?

const validateCred = arr => {
let checkableArr = arr.pop();
for (let i = arr.length - 1; i >= 0; i--) {
arr[i]
checkableArr.push(arr[i])
}
}
When i run the code, I get an error saying that .push() is not a function that I can use on checkableArr. this is because checkableArr isn't an array due to it being a variation of arr (the argument that will be passed when the function is called), which the function isn't sure is an array, is there any way to check that the argument passed into the function is an array?
EDIT:
The thing I was looking for is called isArray(), a method that returns a boolean indicating if the item passed into it is an array or no. Thanks to #David for showing me this tool, along with a bunch of helpful information that helped a lot with writing my program
You're getting that error, because you haven't made sure that the last item of the passed array (arr) is an array itself, but your function's logic requires it to be an array.
There are various ways to solve this, some of them have already been outlined by others (#hungerstar).
Check the last element of arr
One attempt is to ensure that the last element/item inside arr is an array and bail out if it isn't.
const validateCred = arr => {
let lastItem = arr.pop();
if (!Array.isArray(lastItem)) {
throw new Error('validateCred :: Last item of passed array must be an array itself');
}
// ... rest of your code ...
}
Although that does not solve the root cause, it ensures you get a decent and descriptive message about what went wrong. It's possible to improve that by defining a fallback array in case the last item isn't an array itself. Something like this:
const validateCred = arr => {
let lastItem = arr.pop();
let checkableArr = Array.isArray(lastItem) ? lastItem : [];
// ... rest of your code ...
}
One thing to note: If the last item may be an array with a value inside, you have to copy that value into the new array!
const validateCred = arr => {
let lastItem = arr.pop();
let checkableArr = Array.isArray(lastItem) ? lastItem : [lastItem]; // <--
// ... rest of your code ...
}
HINT: The following answer is based on guessing. The name validateCred lets me assume you use it to validate credentials. However, that's just guessing because all the provided code does is taking the last item and then pushing the rest of the contents of arr reversely into it (= reversing and flattening)
Reversing and flattening
If all you want to do with validateCred is reversing and flattening (and you only target supporting environments), you can easily do that with a one-liner:
// impure version
const validateCred = arr => arr.reverse().flat();
// pure version
const validateCred = arr => arr.flat().reverse();
To support older environments as well, you can use .reduce and .concat instead of .flat:
// impure version
const validateCred = arr => arr.reverse().reduce((acc, x) => acc.concat(x), []);
// pure version
const validateCred = arr => arr.reduce((acc, x) => acc.concat(x), []).reverse();
Yes, we pass an array as an argument using call/apply method. In your code when you are using arr.pop() it gets converted to number/string depending upon what type of array you specified, I specified below integer value so checkableArr is now integer so because of this you are getting an error.
Corrected code is here. Just replace in your code like:
let checkableArr = arr; //arr.pop();

Sequentially apply multiple functions to object using different lenses

I would like to perform some updates to an array in an object, and then calculate another parameter based on this update. This is what I tried:
import * as R from 'ramda'
const obj = {
arr: [
2,
3
],
result: {
sumOfDoubled: 0
}
};
const double = a => {
return a*2;
}
const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result','sumOfDoubled']);
const calc = R.pipe(
R.over(arrLens,R.map(double)),
R.view(arrLens),
R.sum,
R.set(res0sumOfDblLens)
);
const updatedObjA = calc(obj);
const updatedObjB = R.set(res0sumOfDblLens,R.sum(R.view(arrLens,R.over(arrLens,R.map(double),obj))),obj);
// what I want: {"arr":[4,6],"result":{"sumOfDoubled":10}}
console.log(JSON.stringify(obj)); //{"arr":[2,3],"result":{"sumOfDoubled":0}}, as expected
console.log(JSON.stringify(updatedObjA)); //undefined
console.log(JSON.stringify(updatedObjB)); //{"arr":[2,3],"result":{"sumOfDoubled":10}}, correct result but the array did not update
I realise that neither approaches will work; approach A boils down to R.set(res0sumOfDblLens,10), which makes no sense as it doesn't have a target object for the operation. Approach B, on the other hand, manipulates the base object twice rather than passing the result of the first manipulation as an input for the second.
How can I achieve this using only one function composition; i.e. apply the double() function to one part of the object, and then passing that updated object as input for calculating sumOfDoubled?
As well as OriDrori's converge solution, you could also use either of two other Ramda functions. I always prefer lift to converge when it works; it feels more like standard FP, where converge is very much a Ramda artifact. It doesn't always do the job because of some of the variadic features of converge. But it does here, and you could write:
const calc = pipe (
over (arrLens, map (multiply (2))),
lift (set (res0sumOfDblLens) ) (
pipe (view (arrLens), sum),
identity
)
)
But that identity in either of these solutions makes me wonder if there's something better. And there is. Ramda's chain when applied to functions is what's sometimes known as the starling combinator, :: (a -> b -> c) -> (a -> b) -> a -> c. Or said a different way, chain (f, g) //~> (x) => f (g (x)) (x). And that's just what we want to apply here. So with chain, this is simplified further:
const arrLens = lensProp('arr')
const res0sumOfDblLens = lensPath(['result', 'sumOfDoubled'])
const calc = pipe (
over (arrLens, map (multiply (2))),
chain (
set (res0sumOfDblLens),
pipe (view (arrLens), sum)
)
)
const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }}
console .log (calc (obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lensProp, lensPath, pipe, over, map, multiply, chain, set, view, sum} = R </script>
To get the updated value, and the object, so you can set the new sum, you can use R.converge():
const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result', 'sumOfDoubled']);
const calc = R.pipe(
R.over(arrLens, R.map(R.multiply(2))),
R.converge(R.set(res0sumOfDblLens), [
R.pipe(R.view(arrLens), R.sum),
R.identity
])
);
const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }};
const result = calc(obj);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Maybe a variant without a lense would be a better fit for your case?
const doubleArr = pipe(
path(['arr']),
map(x => x*2)
)
const newData = applySpec({
arr: doubleArr,
result: {
sumOfDoubled: pipe(
doubleArr,
sum
)
}
})

Array method that does nothing

I like functional programming, it keeps my code, especially scopes, cleaner.
I found myself having a pretty heavy Array manipulation in my code, like this:
this.myArray = someArray
.slice(0, n)
.map(someFunction)
// more manipulation;
if (condition) {
this.myArray = this.myArray.reverse();
}
this.myArray = this.myArray
.reduce(anotherFunction, [])
// even more manipulation
Is there some built-in way to join the if to my functional chain? Something like:
this.myArray = someArray
.slice(0, n)
.map(someFunction)
// ... more manipulation
[condition ? 'reverse' : 'void']()
.reduce(anotherFunction, [])
// ... even more manipulation
The void() method doesn't exist. Is there an alternative? Is it popular approach to merge multiple calls to a single chain, even if that means calling methods that do nothing?
I know I can add my own method to Array:
Array.prototype.void = function () {
return this;
}
But that's not the point. Is there any standard/built-in way to achieve the same effect?
As neutral function, you could take
Array#concat, which returns a new array with old items, or
Array#slice, which returns as well a new array.
Is it popular approach to merge multiple calls to a single chain, even if that means calling methods that do nothing?
No. The usual approach is to split the chain, similar to how you wrote it in your first snippet, when there is an optional step. You wouldn't however repeatedly assign to this.myArray, you would use constant temporary variables:
const array1 = someArray.slice(0, n).map(someFunction) // more manipulation;
const array2 = condition ? array1.reverse() : array1;
this.myArray = array2.reduce(anotherFunction, []) // even more manipulation
That said, in functional programming that uses functions, not methods, you sometimes do find the approach of having a configurable chain. They don't need a void method on the object, they just use the identity function.
Example in Haskell:
let maybeReverse = if condition then reverse else identity
let myArray = fold anotherFunction [] $ maybeReverse $ map someFunction $ take n someArray
Example in JavaScript (where you don't have as many useful builtins and need to write them yourself):
const fold = (fn, acc, arr) => arr.reduce(fn, acc);
const reverse = arr => arr.reverse(); // add .slice() to make pure
const identity = x => x;
const map = (fn, arr) => arr.map(fn);
const take = (n, arr) => arr.slice(0, n);
const maybeReverse = condition ? reverse : identity;
const myArray = fold(anotherFunction, [], maybeReverse(map(someFunction, take(n, someArray)))));
Btw, in your particular example I wouldn't use reverse at all, but rather conditionally switch between reduce and reduceRight :-)
Another option is to switch reduce() with reduceRight() which wouldn't add an extra step at all for the case shown
this.myArray = someArray
.slice(0, n)
.map(someFunction)
[condition ? 'reduce' : 'reduceRight'](anotherFunction, [])

Categories