Compose javascript functions with more than one argument - javascript

How can I convert this function composition into more readable format?
funcA(argumentA, funcB(argumentA, funcC(argumentA, argumentB)))
What I'd like to achieve is something more like this:
compose(funcC, funcB, funcA)(argumentA, argumentB)
I'm using this compose function implementation:
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))
The problem is I need argumentA in all function calls as a first parameter and every function returns a value to be passed as a second parameter to the next function. I know I could create separate function-returning functions and use them like so:
compose(funcCWithArg(argumentA), funcBWithArg(argumentA), funcAWithArg(argumentA))(argumentB)
but in my actual case there aren't only three of them, but many more and that would require some serious amount of code just to write them down. Is there a simpler way to do that?
EDIT: I can't use any external library. Only vanilla js.

Using vanilla JS,
const compose = (...fns) => (arg1, arg2) => fns.reduce((arg, f) => f(arg1, arg), arg2);
Explanation
compose becomes a function returning a function, which loops through the list of functions passed to it, passing the first argument to every function call.
Test
const sum = (a, b) => (a + b);
const mult = (a, b) => (a * b);
compose(sum, mult)(2, 3) === mult(2, sum(2, 3)); // true

It's not hard to write a function like this:
const link = (...fns) => (a, ...args) =>
fns.slice(1).reduce((val, fn) => fn(a, val), fns[0](a, ...args));
If you wanted it to be robust enough to handle errors gracefully, it would take more. But this should be a start.
You would use it like this:
const funcA = (x, y) => `funcA(${x}, ${y})`;
const funcB = (x, y) => `funcB(${x}, ${y})`;
const funcC = (x, y) => `funcC(${x}, ${y})`;
link(funcA, funcB, funcC)('argumentA', 'argumentB');
//=> "funcC(argumentA, funcB(argumentA, funcA(argumentA, argumentB)))"
You can see this in action on Runkit.
(And obviously you can do a reverse if you want the opposite argument order.)

First create new functions that are partial applications using bind. Then use the compose function you already have:
const funcA = (x, y) => `A(${x}, ${y})`;
const funcB = (x, y) => `B(${x}, ${y})`;
const funcC = (x, y) => `C(${x}, ${y})`;
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const partials = (...fns) => (...args) => fns.map((f) => f.bind(this, ...args));
console.log(compose(...partials(funcA, funcB, funcC)("a"))("b"));
// Output:
// A(a, B(a, C(a, b)))
UPDATE
You can also build a single function that composes the partials with the first argument passed and then calls them with the other arguments. (I think this is what you want? I wasn't 100% sure what to do with more than two arguments.)
const partialCompose = (...fns) => (...args) => compose(...partials(...fns)(args[0]))(...args.slice(1));
console.log(partialCompose(funcA, funcB, funcC)("a", "b")); // same output as above

Related

Apply same arguments to Lodash flow callbacks

The Lodash flow() function works, as shown in this question, by giving it a bunch of functions in an array. The result of the previous function will be applied to the next one.
And this is exactly what is problematic for my use case, as it throws away any arguments provided. I need to pass them on to the next function in line, instead of just the return value *).
*) Yes, the return value should get returned modified and be passed on the next function
Using flow() is just what I came up with. Any other solution achieving the goal is ok as well.
What it does:
_.flow( [ firstFn, secondFn ] )
( first, second, third ) => firstFn( first, second, third ) => return first * first
( first ) => secondFn( first )
What it should do:
_.flow( [ firstFn, secondFn ] )
( first, second, third ) => firstFn( first, second, third ) => return first * first
( first, second, third ) => secondFn( resultFromFirstFn, second, third )
Here's some code to demo it:
const checkLog = ( val, attr, group ) => {
console.log( val, attr, group )
return val
}
// This basically is "easy to use"-API and what will be mostly used.
const Callbacks = {
demo: [ _.trim, _.toLower, checkLog ],
}
// _.cond() is just a switch
// [ <condition-to-exec-cb>, <cb-if-cond-true> ]
const cond = _.cond( _.map(
[
[ _.stubTrue, Callbacks.demo ],
],
// Monkey patching _.flow() to each array of cond/cb above
cb => _.set( cb, '1', _.flow( _.last( cb ) ) )
) )
const res = _.map( {
" city": " Sample City",
"street": "Sample Street",
}, cond )
console.log( res )
Note: In case you wonder about why I write it like this: There's a single line comment that points to which part will get altered and extended a lot in the future and this must, due to the target group working there, be simple. So it's just an array of stacked callbacks.
The problem with _.flow() is that it expects everything after the first argument to be a unary function so it can pass the result through all of them. This presents a challenge when you want to apply the same set of arguments to all function and only alter the first one. There are a few ways you could go about this.
Using Lodash
You could leverage _.partialRight to do a partial allocation of every function. partialRight will apply arguments from right to left depending on how many arguments the function takes.
const fn = (a, b, c, d) => console.log(a, b, c, d);
// behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
const newFn = _.partialRight(fn, "yak", "zebra");
newFn("alpaca", "beaver");
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
However you can still pass arguments left to right and this would push the partially applied arguments to right:
const fn = (a, b, c, d) => console.log(a, b, c, d);
// behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
const newFn = _.partialRight(fn, "yak", "zebra");
newFn("alpaca", "beaver", "cat");
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
Assuming all of the function will have at most take n amount of arguments we can:
Get an array of n arguments to be applied to all functions.
Get all functions.
Do partialRight on all functions using all but the first argument. Since we've applied n-1 arguments to each, now all of them can be called as if they are unary.
Use flow on the new functions from step 3.
Start the chain using the first argument.
This will then call all functions such that the first argument is the last result and the second and onwards arguments would be based on the initial ones thus always the same for all functions
function nAryFlow(argsArray, ...fns) { //1. and 2. - get arguments and functions
const start = argsArray[0];
const rest = argsArray.slice(1);
const convertedFns = fns.map(f => _.partialRight(f, ...rest)) //3. Turn all functions into unary ones
return _.flow( //4. `flow` through the functions
...convertedFns
)(start); //5. use the initial fist argument to kick things off
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const args = ["Alice", "Bob", "Carol"];
const result = nAryFlow(args, fn1, fn2, fn3);
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
Without using Lodash
All of the above can very easily be done without Lodash. Instead, we can use Array#reduce to go through all of the functions and apply the arguments. This time, we're directly applying them instead of pre-processing the functions but the overall operation and effect is the same:
The first function takes all the arguments.
Any further function takes the result of the last function and the second argument onwards from the start:
function nAryFlow(argsArray, ...fns) {
const start = argsArray[0];
const rest = argsArray.slice(1);
return fns.reduce((last, f) => f(last, ...rest), start);
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const args = ["Alice", "Bob", "Carol"];
const result = nAryFlow(args, fn1, fn2, fn3);
console.log(result)
Variation using more higher order functions
Just as a variation, this could be split up into multiple higher order functions which might produce a nicer syntax for some situations nAryFlow(f1, f2, f3, fN)(arg1, arg2, arg3, argN):
function nAryFlow(...fns) {
return function (...args) {
const start = args[0];
const rest = args.slice(1);
return fns.reduce((last, f) => f(last, ...rest), start);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const chain = nAryFlow(fn1, fn2, fn3);
const result1 = chain("Alice", "Bob", "Carol");
const result2 = chain("Rachel Green", "Monica Geller", "Chandler Bing");
console.log(result1);
console.log(result2);
Using a Functor
For a slightly different take here, you can use an algebraic structure called Functor to ease the syntax. The essential thing about functors is that they have a .map() method that accepts a function. If you're reminded of Array#map then you're not wrong.
The basic idea is that the functor holds a value and allows it to be modified with functions via .map(). It can then dictate how the function is applied to its value. The result of .map is always the same type of functor as what was already mapped, so you can always continue mapping and be sure that the application would be the same every time. With arrays you always get a new array of the same length with every member transformed. Other functors can apply the function given to .map() different to what an array does but it would always be consistent.
So, background done, here is how this functor can look like:
class NAryFlow {
constructor(argsArray) {
this.value = argsArray[0];
this.rest = argsArray.slice(1);
}
static of(argsArray) {
return new NAryFlow(argsArray);
}
map(fn) {
return NAryFlow.of(
[ fn(this.value, ...this.rest), ...this.rest ]
);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const result = NAryFlow.of(["Alice", "Bob", "Carol"])
.map(fn1)
.map(fn2)
.map(fn3)
.value;
console.log(result)
A similar idea as the two others above - we take the arguments, and apply them all to each function we give to .map(). Every next time we call .map() the first argument would be the last result.
And here is a slight variation using ES6 getters. I think it has a slightly better syntax but wanted to keep the previous implementation simpler.
class NAryFlow {
constructor(argsArray) {
this.args = argsArray;
}
static of(argsArray) {
return new NAryFlow(argsArray);
}
map(fn) {
return NAryFlow.of(
[ fn(...this.args), ...this.rest ]
);
}
get value() {
return this.args[0];
}
get rest() {
return this.args.slice(1);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const result = NAryFlow.of(["Alice", "Bob", "Carol"])
.map(fn1)
.map(fn2)
.map(fn3)
.value;
console.log(result)
flow doesn't throw any arguments, it's just how javascript functions work, they don't respect the a particular signature, you can call a function that only takes 1 parameter with 100 parameters and that won't throw an error.
Also flow is defined such that it passes the initial parameters all to the first function regardless of whether it's going to use them all or not.
You can create your own flow function(since it is a simple one) that only passes the exact amount of parameters a function needs and keep the rest for the rest of functions. To know how many parameters a function takes, you can check its length property like so:
function flow(funcs) {
const length = funcs.length;
for(let func of funcs) {
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
}
return function(...args) {
let result = args[0];
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length)); // call this function with only as many args as it needs
args = [result, ...args.slice(func.length)]; // args becomes the result + the rest of args after the previous call
}
return result;
}
}
Note: This won't work if one of the functions uses the rest parameters, in that case length will return 0. You can't know how many parameters that functions takes. If you wan to call that function with all the available parameters, then just change the for loop above to:
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length || +Infinity)); // if 'func.length' is 0, '+Infinity' is used instead which will use all the available args
args = [result, ...args.slice(func.length || +Infinity)]; // same goes here, '.slice(+Infinity)' will result in an empty array. 'result' will always be present which is the expected behavior
}
Demo:
function flow(funcs) {
const length = funcs.length;
for(let func of funcs) {
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
}
return function(...args) {
let result = args[0];
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length)); // call this function with only as many args as it needs
args = [result, ...args.slice(func.length)]; // args becomes the result + the rest of args after the previous call
}
return result;
}
}
function square(n) {
return n * n;
}
function add(a, b, c) {
return a + b + c;
}
console.log(flow([square, add])(2, 3, 4));

With flow types, is it possible to auto infer return value for function that takes another function as an argument?

Let's say I have a function that takes a function as an argument and returns another function that takes a value that it passes to the function passed as an argument:
const curry = f => x => f(x);
Is it possible to type this function using Flow Type in such a way that the return value of curry is determined by the function f passed in if that function has been typed?
I tried this:
const numToStr = (x: number): string => x.toString();
const curry = <F: Function>(f: F): (mixed => *) => (x: mixed): * => f(x);
const curried = curry(numToStr);
link
The result of passing the first argument is that the resulting function curried has a signature like this:
(((x: mixed) => any) | ((x: mixed) => empty))
This makes sense, either the result is empty or it's anything. What I'd hoped is that because numToStr is typed, that it'd be possible for Flow to understand that curried is really number => string.
Is there a way to do this that I've missed? If not, can anyone explain why.
Currying usually applies to the process of taking a function of two arguments and transforming it into a function of one argument that returns another function of one argument which returns the result.
In Flow it would look like this:
const curry = <A, B, C>(f: (A, B) => C): (A => (B => C)) => (x) => (y) => f(x, y);
const prefixNum = (prefix: string, x: number): string => prefix + x.toString();
const prefixed = curry(prefixNum);
const withHello = prefixed("hello ");
const result = withHello(3); // "hello 3"
Your version is a bit different. You are transforming a function of one argument into a function of zero arguments. This is more like lazy evaluation than currying: supply all of the arguments but only return a zero-argument function (sometimes called a "thunk"), which actually performs the computation when you call it. The implementation could look like this:
const makeLazy = <A, C>(f: A => C): (A => (() => C)) => (x) => () => f(x);
It's essentially the definition above, but with one of the types replaced with (). This should work how you want it to:
const numToStr = (x: number): string => x.toString();
const lazyNumToStr = makeLazy(numToStr);
const thunk = lazyNumToStr(3);
thunk(); // "3"

Curry function without partial application?

I am following a tutorial on firebase authentication in ReactJS. In the code, I stumbled across such function:
const byPropKey = (propertyName, value) => () => ({
[propertyName]: value,
});
I suppose this function is used to set state of a React component. It is used like so:
<input
value={username}
onChange={event => this.setState(byPropKey('username', event.target.value))}
type="text"
placeholder="Full Name"
/>
I am a newcomer to React, JavaScript, ES6 and functional programming in general, so this confuses the hell out of me. I know that the double arrow notation indicates a curried function, which enables partial application of the function. In this case, however, I can not see how this gets used in such scenario.
Here is the tutorial I am referring to:
tutorial
I can not see how this gets used in such scenario.
The tutorial author is currying that function because setState can take either an object or a function. A function gives you access to the previous state and props. Here is a sandbox example.
In the example you provided, the tutorial author is not using the partial application, and thus not leveraging the access to previous state and props. So in this specific case, a non curried implementation (passing an object to setState) would do just fine.
However, if you were to add to this tutorial codebase, the future code might require byPropKey to access the prevState and props:
const byPropKey = (propertyName, value) => (prevState, props) => {
//... do something with prevState and props
return {
[propertyName]: value,
}
};
... which is probably why the tutorial author wrote the function curried.
byPropKey
function uses as helper to map state and returns result like:
{ "statePiceName": value }
to set your state.
Same behavior without this function:
{event => this.setState({ username: event.target.value })}
Also you can just console.log(byPropKey('key', 'customValue')) and see how its works.
You misunderstand the double arrow. This just creates a function, not necessarily a curried or partial function.
For example:
// just a function
let doSomething = () => console.log("did something")
//call it
doSomething()
You can return another function from a function (aka a higher-order function), which is what's going on in your example:
// this will return the function () => console.log("said " + something)
let saySomething = (something) => () => console.log("said " + something)
// call the above function, which returns a different function
let sayHello = saySomething("Hello")
// now call that function
sayHello()
You can make partial functions with bind:
function add(a, b) {console.log(a + b)}
let addFive = add.bind(null, 5)
addFive(3)
addFive(10)
To take it one more step, you can make a generic function to add whatever:
// just a regular function
let add = (a,b) => console.log(a + b)
// a function to create a partial of the above based on passed in argument
let addThis = (a) => add.bind(null, a)
// make an add-10 function
let addTen = addThis(10)
// call it
addTen(12)
// make an add-33 function
let add33 = addThis(33)
add33(100)
EDIT: In response to the comments
Curried functions are those the break down larger functions that take multiple arguments into smaller functions that take fewer or one argument.
For example this simple function:
let add = (a, b, c) => a + b + c
can be curried into three functions that each take a single argument like this:
let addCurried = (a) => (b) => (c) => a + b + c
You can call this with some or all the arguments to get the result or a partial:
// non curried: F(a, b, c) -> sum
// curried: F():a -> (b -> (c -> sum))
let addCurried = (a) => (b) => (c) => a + b + c
// add everything
let total = addCurried(1)(2)(3) // -> 6
// make a partial
let addTwoAndThree = addCurried(2)(3)
// call it
let total2 = addTwoAndThree(100) //-> 105
console.log(total, total2)
It's hard to see given the definition of currying how a function that accepts two arguments and returns function the accepts none is considered currying that function.

JavaScript functional programing with compose function and Array.reduce

I am new to JavaScript Functional programming. In code below, compose can't work without setInterval outside it and clear as the first argument also does't give the compose initial value.
So my question is how can compose work without the setInterval?
const clear = () => console.clear()
const f1 = () => 2
const log = message => console.log(message)
const compose = (...fns) =>
arg =>
fns.reduce(
(composed, f) => f(composed),
arg
)
setInterval(
compose(clear, f1, log), 1000
)
compose(...fns) returns a function. When used with setInterval, it is being called implicitly by the JavaScript engine.
If you want to use it directly, you can do something like:
const clear = () => console.clear()
const f1 = () => 2
const log = message => console.log(message)
const compose = (...fns) =>
arg =>
fns.reduce(
(composed, f) => f(composed),
arg
)
compose(clear, f1, log)();

Is using sequence the right approach to give monadic arguments to a function of arity greater than 1?

See the following code snippet:
const
fun1 = () => Either.of(1),
fun2 = () => Either.of(2),
fun3 = () => Either.of(3),
fun4 = curry((x, y, z) => Either.of(x + y + z)),
fun5 = x => Either.of(x + 1),
fun6 = () => pipeK(
() => sequence(Either.of, [fun1(), fun2(), fun3()]),
apply(fun4),
fun5
)(),
result = fun6() // returns 7
fun4 requires 3 arguments and I'd like to give them only if all of them are right arguments. That is, sequence will apply each monadic value so I'll get them as a single right containg the raw fun1, fun2, fun3 return values.
Is this the recommended approach?
Click here for run the whole code snippet
No, I would not use sequence with an array and apply. I think the more idiomatic approach is to use ap:
const fun6 = () => chain(fun5, unnest(ap(ap(ap(Either.of(fun4), fun1()), fun2()), fun3())));
// or
const fun6 = () => chain(fun5, unnest(ap(ap(map(fun4, fun1()), fun2()), fun3())));
// or
const fun6 = () => Either.of(fun4).ap(fun1()).ap(fun2()).ap(fun3()).chain(identity).chain(fun5);
The equivalent in Haskell would be fun5 =<< join (fun4 <$> fun1 <*> fun2 <*> fun3). The unnest is needed when fun4 returns an Either, which might not be necessary.

Categories

Resources