Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional composition in JavaScript

I know this is quite possible since my Haskell friends seem to be able to do this kind of thing in their sleep, but I can't wrap my head around more complicated functional composition in JS.

Say, for example, you have these three functions:

const round = v => Math.round(v);

const clamp = v => v < 1.3 ? 1.3 : v;

const getScore = (iteration, factor) =>
    iteration < 2 ? 1 :
    iteration === 2 ? 6 :
    (getScore(iteration - 1, factor) * factor);

In this case, say iteration should be an integer, so we would want to apply round() to that argument. And imagine that factor must be at least 1.3, so we would want to apply clamp() to that argument.

If we break getScore into two functions, this seems easier to compose:

const getScore = iteration => factor =>
    iteration < 2 ? 1 :
    iteration === 2 ? 6 :
    (getScore(iteration - 1)(factor) * factor);

The code to do this probably looks something like this:

const getRoundedClampedScore = compose(round, clamp, getScore);

But what does the compose function look like? And how is getRoundedClampedScore invoked? Or is this horribly wrong?

like image 544
Andrew Avatar asked Apr 02 '26 05:04

Andrew


2 Answers

The compose function should probably take the core function to be composed first, using rest parameters to put the other functions into an array, and then return a function that calls the ith function in the array with the ith argument:

const round = v => Math.round(v);

const clamp = v => v < 1.3 ? 1.3 : v;

const getScore = iteration => factor =>
    iteration < 2 ? 1 :
    iteration === 2 ? 6 :
    (getScore(iteration - 1)(factor) * factor);

const compose = (fn, ...transformArgsFns) => (...args) => {
  const newArgs = transformArgsFns.map((tranformArgFn, i) => tranformArgFn(args[i]));
  return fn(...newArgs);
}

const getRoundedClampedScore = compose(getScore, round, clamp);

console.log(getRoundedClampedScore(1)(5))
console.log(getRoundedClampedScore(3.3)(5))
console.log(getRoundedClampedScore(3.3)(1))
like image 180
CertainPerformance Avatar answered Apr 03 '26 17:04

CertainPerformance


Haskell programmers can often simplify expressions similar to how you'd simplify mathematical expressions. I will show you how to do so in this answer. First, let's look at the building blocks of your expression:

round    :: Number -> Number
clamp    :: Number -> Number
getScore :: Number -> Number -> Number

By composing these three functions we want to create the following function:

getRoundedClampedScore :: Number -> Number -> Number
getRoundedClampedScore iteration factor = getScore (round iteration) (clamp factor)

We can simplify this expression as follows:

getRoundedClampedScore iteration factor = getScore (round iteration) (clamp factor)
getRoundedClampedScore iteration        = getScore (round iteration) . clamp
getRoundedClampedScore iteration        = (getScore . round) iteration . clamp
getRoundedClampedScore iteration        = (. clamp) ((getScore . round) iteration)
getRoundedClampedScore                  = (. clamp) . (getScore . round)
getRoundedClampedScore                  = (. clamp) . getScore . round

If you want to convert this directly into JavaScript then you could do so using reverse function composition:

const pipe = f => g => x => g(f(x));

const compose2 = (f, g, h) => pipe(g)(pipe(f)(pipe(h)));

const getRoundedClampedScore = compose2(getScore, round, clamp);

// You'd call it as follows:

getRoundedClampedScore(iteration)(factor);

That being said, the best solution would be to simply define it in pointful form:

const compose2 = (f, g, h) => x => y => f(g(x))(h(y));

const getRoundedClampedScore = compose2(getScore, round, clamp);

Pointfree style is often useful but sometimes pointless.

like image 36
Aadit M Shah Avatar answered Apr 03 '26 19:04

Aadit M Shah



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!