Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Higher Order Function Composition

I'm trying to figure out how I can declaritively define a function as a composition of two other functions in Kotlin but I'm struggling. Here is my code:

fun compose(a: (Int, Int) -> Int, b: (Int, Int) -> Int): Int {
    return a.invoke() + b.invoke()
}

The idea of the compose function is that it will take in two functions as it's input (both of which takes two Ints and return an Int) and return the sum of the results of the two passed functions. The problem is I have to invoke the passed functions to work out their sum (obviously lol) but I don't know the values which I wish to invoke with inside the compose method (they are the values passed to the functions).

Am I completely missing something here? I know this is possible in a language like Haskell, is it possible or not in Kotlin?

like image 886
Thomas Cook Avatar asked Feb 22 '18 16:02

Thomas Cook


3 Answers

One way:

You have to pass the two Ints as additional arguments to compose like this:

fun compose(
    c: Int, d: Int, a: (Int, Int) -> Int, b: (Int, Int) -> Int
) = a(c, d) + b(c, d)

Lambdas are a further level of abstraction which give you the opportunity to make behaviour variable, you still have to provide them with data.

A more abstract approach:

You can abstract even further and let compose return a lambda which combines the results of the other two lambdas (lets call it compose2):

// return type is inferred to (Int, Int) -> Int
fun compose2(a: (Int, Int) -> Int, b: (Int, Int) -> Int) = { 
     c: Int, d: Int -> a(c, d) + b(c, d)
}

val f = compose2(/* pass lambdas */)

f is a lambda itself an can be called like this:

f(2, 4)

So, compose2 does nothing more than return a lambda which adds the results of the two passed lambdas. The actual invocation is done outside of compose2.

An even more abstract approach:

In your compose function you use a simple addition as composition operation. You can even make this operation variable, by passing a third lambda ab which tells your function how to compose a and b:

fun compose3(
    a: (Int, Int) -> Int, b: (Int, Int) -> Int, ab: (Int, Int) -> Int
) = { 
    c: Int, d: Int -> ab(a(c, d), b(c, d))
}

The result is, again, a lambda which takes two Ints and returns one Int.

like image 182
Willi Mentzel Avatar answered Oct 13 '22 21:10

Willi Mentzel


You need a new function (Int, Int) -> Int, which is the sum of two (Int, Int) -> Int functions.

Therefore, what compose(...) returns is another (Int, Int) -> Int typed function.

Now, we have the type of the function compose. It returns a function, not an Int value.

fun compose(a: (Int, Int) -> Int, b: (Int, Int) -> Int): (Int, Int) -> Int

How about its body?

It will return a function. Let's just return a lambda expression { x: Int, y: Int -> a(x, y) + b(x, y) }.

fun compose(a: (Int, Int) -> Int, b: (Int, Int) -> Int): (Int, Int) -> Int {
    return { x: Int, y: Int -> a(x, y) + b(x, y)}
}

Now we can omit all the unnecessary parts.

fun compose(a: (Int, Int) -> Int, b: (Int, Int) -> Int): (Int, Int) -> Int =
    { x, y -> a(x, y) + b(x, y) }

That's it.

like image 21
Naetmul Avatar answered Oct 13 '22 21:10

Naetmul


Another worthwhile approach is to use infix extension function. For your case with parameters of type Int it can be like this:

// Extension function on lambda
private infix fun ((Int, Int) -> Int).plus(f: (Int, Int) -> Int) = {
    p1: Int, p2: Int, p3: Int, p4: Int -> this(p1, p2) + f(p3, p4)
}

// Usage
val first: (Int, Int) -> Int = { a, b -> a + b }
val second: (Int, Int) -> Int = { a, b -> a - b }

// first two parameters (1 and 2) for the `first` lambda, 
// second two parameters (4 and 3) for the `second` lambda
val sum = (first plus second)(1, 2, 4, 3) // result is 4
like image 1
Sergey Avatar answered Oct 13 '22 23:10

Sergey