aleclofabbro
aleclofabbro

Reputation: 1699

Pointfree composed function with spreaded arguments

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

Answers (1)

Are
Are

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:

  1. We supply our add function signature instead of (E -> F)

     (E -> F     )
     (A -> A -> A)
    

    We conclude that

     E = A
     F = A -> A
    
  2. 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

Related Questions