3

I want to do some function composition. 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 composition of node.js/express middleware functions.)

Mulan
  • 129,518
  • 31
  • 228
  • 259
matths
  • 760
  • 9
  • 19
  • 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 composed: http://stackoverflow.com/a/42139851/633183 – Mulan Feb 10 '17 at 17:59
  • I wrote another answer an hour ago about composition in an existing code base. I think you will find it relevant to thsi topic: http://stackoverflow.com/a/42164779/633183 – Mulan Feb 10 '17 at 18:00
  • Your "problem" is that you're trying to compose functions of various *arity*; a unary function with a binary function. Function composition 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 Feb 10 '17 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 Feb 11 '17 at 11:22

2 Answers2

6

In 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...

a bad dream

  • 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 combined in various ways.
  • What is the difference between f1 and f2? 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 in f2?
    • 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?

Recall that function composition 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.

a nigthmare

  • Is there even a pattern here? Maybe, but you still have the awkward unary function f1 that only gets one arg, while f2 and f3 each get 2
  • Is it true that f2 and f3 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, but f2 chains from the right?
    • I can't imagine your entire API of binary functions would magically need chained arguments in the same place
  • You've already mixed unary with binary functions in your composition; 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 composition is being misused here. Function composition pretty much only works when you're composing 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 combination 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 compose 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 compose = ([f,...fs]) => x =>
  f === undefined ? x : f (compose (fs) (x))
                  
let f = compose([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 complex flow) with a single line of code.

Mulan
  • 129,518
  • 31
  • 228
  • 259
2

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 composeFuncs(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 = composeFuncs(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.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Of course I could do this, but I then probably have to take care of the handling of 'this' as _.flowRight from lodash is doing. As I am quite new to doing much in the functional programming way, I thought, there might be a well known pattern for something like what I need. :) – matths Feb 10 '17 at 15:26
  • @matths Modified to include `this` propagation. Could you give an example of where you want to use this? I'm not familiar of a a well-known pattern for this, but there might be one. – JLRishe Feb 10 '17 at 15:42
  • I am currently writing express/connect like middleware functions, e.g. one is `method(m, handler)` where m can be get, post, put, etc. and another might be `path(p, handler)`. Composition should be `request(m, p, handler)` which runs `method(m, path(p, handler))`. Each middleware return value is a handler function by itself. I am not sure, I can explain well enough. – matths Feb 10 '17 at 16:01
  • @matths in general, functional programming largely does not concern itself with context `this` – JS is multiparadigm and `this` is primarily suited for writing programs in OOP style – Mulan Feb 10 '17 at 18:03