I'm trying to write a function that compares two items using another function, then checks if the result is greater than some other value also provided to the function. I can write it like this:
const compareDifference = function(t1, t2, threshold) {
return getDifference(t1, t2) > threshold;
};
... but this doesn't seem very functional. Every example I find for classical composition assumes that I'll know the value to be compared against before the function is called, in which case I could write it functionally like so:
const greaterThan = (x, y) => x > y;
const greaterThan10 = _.partial(greaterThan, _, 10);
const compareDifference = _.compose(greaterThan10, getDifference);
Since I'm relatively new to functional programming, I feel like I'm missing something easy or fundamental here. Is there a way to write the function so that it incorporates the parameter to be passed to greaterThan without me having to mention it explicitly? Ideally it would be something like:
const compareDifference = _.compose(_.partial(greaterThan, _), getDifference);
I think LUH3417's answer is great for beginners. It touches on some basics but I think there's room for some other info
First, if you wanted the exact same API in your original question, you could break it down into parts like this.
const comp = f=> g=> x=> f (g (x))
const comp2 = comp (comp) (comp)
const flip = f=> x=> y=> f (y) (x)
const sub = x=> y=> y - x
const abs = x=> Math.abs
const diff = comp2 (Math.abs) (sub)
const gt = x=> y=> y > x
// your function ...
// compose greaterThan with difference
// compareDifference :: Number -> Number -> Number -> bool
const compareDifference = comp2 (flip(gt)) (diff)
console.log(compareDifference (3) (1) (10))
// = gt (10) (abs (sub (1) (3)))
// = Math.abs(1 - 3) > 10
// => false
console.log(compareDifference (5) (17) (10))
// = gt (10) (abs (sub (5) (17)))
// = Math.abs(17 - 5) > 10
// => true
But, you are right to have suspicion that your original code doesn't feel that functional. The code I gave you here works, but it still feels... off, right ? I think something that would greatly improve your function is if you made it a higher-order function, that is, a function that accepts a function as an argument (and/or returns a function).
The Yellow Brick Road
We could then make a generic function called testDifference
that takes a threshold function t
as input and 2
numbers to base the threshold computation on
// testDifference :: (Number -> bool) -> Number -> Number -> bool
const testDifference = t=> comp2 (t) (diff)
Looking at the implementation, it makes sense. To test the difference, we need a test (some function) and we need two numbers that compute a difference.
Here's an example using it
testDifference (gt(10)) (1) (3)
// = gt (10) (abs (sub (1) (3)))
// = Math.abs(1 - 3) > 10
// = Math.abs(-2) > 10
// = 2 > 10
// => false
This is a big improvement because >
(or gt
) is no longer hard-coded in your function. That makes it a lot more versatile. See, we can just as easily use it with lt
const lt = x=> y=> y < x
testDifference (lt(4)) (6) (5)
// = lt (4) (abs (sub (6) (5)))
// = Math.abs(5 - 6) < 4
// = Math.abs(-1) < 4
// = 1 < 4
// => true
Or let's define a really strict threshold that enforces the numbers have a exact difference of 1
const eq = x=> y=> y === x
const mustBeOne = eq(1)
testDifference (mustBeOne) (6) (5)
// = eq (1) (abs (sub (6) (5)))
// = Math.abs(5 - 6) === 1
// = Math.abs(-1) === 1
// = 1 === 1
// => true
testDifference (mustBeOne) (5) (8)
// = eq (1) (abs (sub (5) (8)))
// = Math.abs(8 - 5) === 1
// = Math.abs(3) === 1
// = 3 === 1
// => false
Because testDifference
is curried, you can also use it as a partially applied function too
// return true if two numbers are almost the same
let almostSame = testDifference (lt(0.01))
almostSame (5.04) (5.06)
// = lt (0.01) (abs (sub (5.04) (5.06)))
// = Math.abs(5.06 - 5.04) < 0.01
// = Math.abs(0.02) < 0.01
// = 0.02 < 0.01
// => false
almostSame (3.141) (3.14)
// = lt (0.01) (abs (sub (3.141) (3.14)))
// = Math.abs(3.14 - 3.141) < 0.01
// = Math.abs(-0.001) < 0.01
// = 0.001 < 0.01
// => true
All together now
Here's a code snippet with testDifference
implemented that you can run in your browser to see it work
// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f=> g=> x=> f (g (x))
// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = comp (comp) (comp)
// sub :: Number -> Number -> Number
const sub = x=> y=> y - x
// abs :: Number -> Number
const abs = x=> Math.abs
// diff :: Number -> Number -> Number
const diff = comp2 (Math.abs) (sub)
// gt :: Number -> Number -> bool
const gt = x=> y=> y > x
// lt :: Number -> Number -> bool
const lt = x=> y=> y < x
// eq :: a -> a -> bool
const eq = x=> y=> y === x
// (Number -> bool) -> Number -> Number -> bool
const testDifference = f=> comp2 (f) (diff)
console.log('testDifference gt', testDifference (gt(10)) (1) (3))
console.log('testDifference lt', testDifference (lt(4)) (6) (5))
console.log('testDifference eq', testDifference (eq(1)) (6) (5))
// almostSame :: Number -> Number -> bool
let almostSame = testDifference (lt(0.01))
console.log('almostSame', almostSame (5.04) (5.06))
console.log('almostSame', almostSame (3.141) (3.14))