Reputation: 797
I'm working with this higher-order compose function:
fun <T, U, V> higherCompose(): ((U) -> V) -> ((T) -> U) -> (T) -> V =
{ f ->
{ g ->
{ x -> f(g(x)) }
}
}
And the application:
val cos = higherCompose<Double, Double, Double>()()
{ x: Double -> Math.sin(x)}() { x : Double -> Math.PI/2 - x }
I do understand what we're trying to achieve here mathematically. But what I'm struggling with is notation and the programming meaning:
What exactly is
higherCompose<Double>()
And why do we apply so strangely values to it:
1. () ()
2. { function1} { function 1}
Upvotes: 1
Views: 131
Reputation: 148089
Here's how this notation is parsed and what the function calls are:
higherCompose<Double, Double, Double>()() { x -> Math.sin(x) }() { x -> Math.PI/2 - x }
| ^| ^ ^^^^^^^^^^^^^^^^^^^| ^ ^^^^^^^^^^^^^^^^^^^^^^|
| 1| 2 3 | 4 5 |
|_____________________________________|______________________|_________________________|
| first call | second call | third call |
This is the initial no-argument call to higherCompose
that returns a value of exactly the higherCompose
's return type, ((U) -> V) -> ((T) -> U) -> (T) -> V
, with the three type parameters substituted with Double
.
These parentheses are simply used to resolve parsing ambiguity. Kotlin allows calling a function with a lambda in either of the forms: f({ })
, f() { }
, f { }
. If you remove the parentheses, the lambda marked as (3) will be treated as an argument to the first call. One way to solve this ambiguity is to add ()
to tell the compiler that it's the (1)'s return value that is invoked and not higherCompose
Therefore, this lambda is passed as the ((U) -> V)
argument to the higherCompose
's result type, producing ((T) -> U) -> (T) -> V
.
These parentheses are added because Kotlin does not allow calling a return value of a call with a lambda outside parentheses (f() { }
) directly with another lambda: f() { } { }
is prohibited, but adding another pair of parentheses between the lambdas solves this: f() { }() { }
means an invocation of the f
's result with a lambda.
This lambda is passed as the ((T) -> U)
argument to the (3)'s result type, and the result of this call is (T) -> V
You can get a much less cryptic version of this expression if you pass all the lambdas inside parentheses (the f({ })
form instead of f() { }
or f { }
):
higherCompose<Double, Double, Double>()({ x -> Math.sin(x) })({ x -> Math.PI / 2 - x })
See: Lambda Expressions and Anonymous Functions in the language reference.
Upvotes: 1
Reputation: 2352
Another way to handle function composition in Kotlin is to use an extension function on the function type. Try something like this:
infix fun <P1, R1, R2> ((P1) -> R1).then(f: ((R1)-> R2)) : (P1) -> R2 {
return { p1 : P1 -> f(this(p1)) }
}
This allows you to use a simpler construct in your code:
function1 then function1
Using your example above you can do:
infix fun <P1, R1, R2> ((P1) -> R1).then(f: ((R1)-> R2)) : (P1) -> R2 {
return { p1 : P1 -> f(this(p1)) }
}
fun main() {
val c = { x: Double -> Math.sin(x)} then { x : Double -> Math.PI/2 - x }
println(c(2.4))
}
which prints 0.8953331462437456
.
Check out my Functional FizzBuzz post which uses this approach.
Upvotes: 0