I want to do some function position. I know already this:
If f3(x)
shall be the same as f1(f2(x))
then f3 = _.flowRight(f1,f2);
If f3(x,y)
shall be the same as f1(x, f2(y))
then …?
(The use case is the position of node.js/express middleware functions.)
I want to do some function position. I know already this:
If f3(x)
shall be the same as f1(f2(x))
then f3 = _.flowRight(f1,f2);
If f3(x,y)
shall be the same as f1(x, f2(y))
then …?
(The use case is the position of node.js/express middleware functions.)
Share Improve this question edited Feb 10, 2017 at 19:05 Mulan 136k35 gold badges240 silver badges275 bronze badges asked Feb 10, 2017 at 14:38 matthsmatths 96010 silver badges20 bronze badges 4- I wrote an answer yesterday about lodash's flow. I think you will find it very helpful wrt some functions aren't meant to be posed: stackoverflow./a/42139851/633183 – Mulan Commented Feb 10, 2017 at 17:59
- I wrote another answer an hour ago about position in an existing code base. I think you will find it relevant to thsi topic: stackoverflow./a/42164779/633183 – Mulan Commented Feb 10, 2017 at 18:00
-
Your "problem" is that you're trying to pose functions of various arity; a unary function with a binary function. Function position works best when unary functions are used exclusively. The short answer is
const f3 = (x,y) => f1(x, f2(y))
– let simple be simple. – Mulan Commented Feb 10, 2017 at 18:17 - @naomik thanks for your answer, and you're right about the arity (unary or binary, function with one, two or even more arguments). In my case I could argue, that all of my middleware functions are binary, so have two arguments: the filter value and the handler. So I am quite sure, it's 'generic' to my use-case. – matths Commented Feb 11, 2017 at 11:22
2 Answers
Reset to default 6In the following images, I use
{_}
as a placeholder for a value. Think of it as a hole in the code where we pass something in.
Ok let's imagine what your function would have to do...
- Does this seems like a generic transformation? ie, do you think we can use this in many places? – functional programming promotes building functions which are highly reusable and can be bined in various ways.
- What is the difference between
f1
andf2
?f1
is a unary function which will only get one arg,f2
is a binary function which will get two. Are you going to remember which one goes in which place? - What governs the position that
f1(x)
gets placed inf2
?- Compare
f2(y,f1(x))
... - to
f2(f1(x),y)
- is one of those more useful than the other?
- are you going to remember which position
f1
gets?
- Compare
Recall that function position should be able to chain as many functions together as you want. To help you understand the futility of someFunc
, let's imagine it accepting up to 3 functions and 3 arguments.
- Is there even a pattern here? Maybe, but you still have the awkward unary function
f1
that only gets one arg, whilef2
andf3
each get 2 - Is it true that
f2
andf3
are going need the value of the previous function calls on the right side always ?- Compare
f3(z,f2(y,f1(x)))
- to
f3(f2(y,f1(x)),z)
- Maybe
f3
needs to chain left, butf2
chains from the right? - I can't imagine your entire API of binary functions would magically need chained arguments in the same place
- Compare
- You've already mixed unary with binary functions in your position; why arbitrarily limit it to just functions of those type then? What about a function of 3 or more arguments?
The answer is self-realizing
Function position is being misused here. Function position pretty much only works when you're posing unary functions exclusive (functions accepting 1 argument each). It immediately breaks down and cannot be generalised when mixing in functions of higher arity.
Going back to your code now, if f3
needs a name and it is the bination of f1
, f2
, and two parameters, it should be plainly expressed as …
const f3 = (x,y) => f1(x, f2(y))
Because it makes so many arbitrary choices, it cannot be generalized in any useful way. Just let it be as it is.
"So is there any way to pose functions of varying arity?"
Sure, there are a couple techniques of varied practicality. I'll demonstrate use of the highly practical partial
function here
const partial = (f,...xs) => (...ys) => f(...xs, ...ys)
const add = (x,y) => x + y
const mult = (x,y) => x * y
const sq = x => mult (x,x)
// R.I.P. lodash.flowRight
const pose = ([f,...fs]) => x =>
f === undefined ? x : f (pose (fs) (x))
let f = pose([partial(add, 1), sq, partial(mult, 3)])
console.log(f(2))
// add(1, square(mult(3, 2)))
// add(1, square(6))
// add(1, 36)
// => 37
Oh, by the way, we replaced Lodash's flowRight
(wrapper of the plex flow
) with a single line of code.
It sounds like you have a very specific requirement that may not have a lodash equivalent.
Why not just write your own helper function for this?
function poseFuncs(f1, f2) {
return function(x, y) {
return f1.call(this, x, f2.call(this, y));
};
}
var myObj = {
add: function(val1, val2) {
return this.myVal + val1 + val2
},
mult: function(val) {
return this.myVal * val
},
myVal: 7
};
myObj.newFunc = poseFuncs(myObj.add, myObj.mult);
// 7 + 1 + 7 * 2 = 22
console.log(myObj.newFunc(1, 2));
Edit: Updated to handle this
the same way _.flowRight
does.