Reputation: 1699
I'm trying to figure out if there is a pattern for writing pointfree composed function when the arguments should be spreaded in curried composing functions
i.e (with Ramda):
add_1_and_multiply = (add, mul) => R.compose(R.multiply(mul), R.add(1))(add)
add_1_and_multiply(3, 5) // 20
How to write add_1_and_multiply
in pointfree style?
Upvotes: 1
Views: 83
Reputation: 2241
I'm not sure if you can easily combine pointfree style and non-unary arity. Think first what should be the type of the resulting and composed functions:
// Compose: (B -> C) -> (A -> B) -> A -> C
const compose = f => g => x => f(g(x))
// Add: A -> A -> A
const add = x => y => x + y
// Mul: A -> A -> A
const mul = x => y => x * y
// Add1: A -> A
const add1 = add(1)
// Add1AndMul: A -> A -> A
// because:
// Add1: A -> A
// Mul: A -> A -> A
const add_1_and_mul = compose(mul)(add1)
// Mul4: A -> A
const mul_4 = add_1_and_mul(3)
const result = mul_4(5) //> 20
Ramda has uncurryN
so you can wrap it around the compose
and get rid of the currying of the resulting function.
const add_1_and_multiply = R.uncurryN(2, R.compose(R.multiply, R.add(1)))
let result2 = add_1_and_multiply(3, 5) //> 20
To add another function to the "chain" you need to compose it with previous function.
// Add1AndMul: A -> A -> A
const add1_mul = compose(mul)(add1)
This is our desired signature.
// 1 2 3
// Add1AndMulAndAdd: A -> A -> A -> A
// which is: | | |
// Add1: A -> A | |
// Mul: A -> A -> A |
// Add: A -> A -> A
So somehow we have to pass those A2 and A3 without any "points". Let's try just simple composition and analyze it:
let add1_mul_add = compose(add)(add1_mul)
Remeber signature of compose: (E -> F) -> (D -> E) -> D -> F
!
Analyzing it in steps:
We supply our add
function signature instead of (E -> F)
(E -> F )
(A -> A -> A)
We conclude that
E = A
F = A -> A
We do the same to (D -> E)
and add1_mul
(D -> E )
(A -> A -> A)
We conclude that
D = A
E = A -> A
But we can already see a contradiction there!
Conclusion in step 2 contradicts conclusion in step 1:
E
cannot be A
and A -> A
at the same time.
Therefore we cannot compose add
and add1_mul
and our add1_mul_add
will throw an error.
Let's try to workaround the problem and fix it breaking our promise of pointfree style.
const add1_mul_add = x => compose(add)(add1_mul(x))
I'm going to break some rules and mix signatures with code to illustrate my point:
x -> (A -> A -> A) -> (x -> A -> A) -> A -> A -> A
||
\/
x -> (A -> A -> A) -> (A -> A) -> A -> A -> A
(E -> F ) -> (D -> E) -> D -> F
So we got our correct compose signature! How to get rid of the x
variable to go back to pointfree?
We can try to look for obvious patterns, like for example... our ye olde function composition!
f(g(x)) => compose(f)(g)
And we find this pattern in our new add1_mul_add
-
f = compose(add)
g = add1_mul
f(g(x)) = compose(add)(add1_mul(x))
And we reduce it to pointfree and we got our new add1_mul_add
function:
const add1_mul_add = compose(compose(add))(add1_mul)
But hey - we can reduce it even more!
const add1_mul_add = compose(compose)(compose)(add)(add1_mul)
And there we have found something that already exists in haskell under the name of The Owl
.
We can define it in Javascript simply as:
const owl = compose(compose)(compose)
But now, with every new function in the chain, you would have to create a higher order of the owl operator.
const owl2 = compose(compose)(owl)
const add1_mul_add_mul = owl2(mul)(add1_mul_add)
const owl3 = compose(compose)(owl2)
const add1_mul_add_mul_add = owl3(add)(add1_mul_add_mul)
So I really recommend having your functions unary in pointfree style. Or use other constructs like lists:
const actions = [ add, mul, add, mul ]
const values = [ 1, 2, 3, 4 ]
const add_mul_add_mul = (...values) => zip(actions, values).reduce((acc, [action, value]) => action(acc, value), 0)
Upvotes: 2