18

Since Kotlin supports many concepts from functional programming, I was wondering if there is a way to do partial application of a function in Kotlin as well?

One such example of where partial application can be useful is:

// In one class
fun doSomething(cb: (a, b) -> Unit) {
    <some logic here to compute someField>
    doSomethingElse(cb.applyPartially(someField))
}

// In another class
fun doSomethingElse(cb: (b) -> Unit) {
    <some logic here to compute someOtherField>
    cb(someOtherField)
}
idunnololz
  • 8,058
  • 5
  • 30
  • 46

3 Answers3

21

Out of the box, no. But it isn't too hard to do with a helper function:

    fun add(a: Int, b:Int): Int {
        return a + b
    }

    fun <A, B, C> partial2(f: (A, B) -> C, a: A): (B) -> C {
        return { b: B -> f(a, b)}
    }

    val add1 = partial2(::add, 1)

    val result = add1(2) //3

So partial2 takes a function of 2 arguments and the first argument and applies it to get a function of 1 argument. You would have to write such helpers for all arities you need.

Alternatively, you can do it with an extension method:

fun <A,B,C> Function2<A,B,C>.partial(a: A): (B) -> C {
    return {b -> invoke(a, b)}
}

val abc: (Int) -> Int = (::add).partial(1)
triggerNZ
  • 4,221
  • 3
  • 28
  • 34
  • Now all I need is a way to add an extension function to functions. – idunnololz Oct 09 '18 at 00:54
  • Yeah I was able to get to that point. The issue now is that this only works for the functions with 2 arguments. I am now trying to devise a solution for n argument functions. – idunnololz Oct 09 '18 at 01:09
  • I wrote a javascript script to generate these functions up to function with 20 arguments LOL: https://gist.github.com/idunnololz/56ffb520c164bef13766dc0fb551ee0d – idunnololz Oct 09 '18 at 01:47
  • Isn't a lambda, e.g. `doSomethingElse{ cb(someField, it) }`, a much simpler solution to this?  (Disclaimer: I'm not a FP developer…) – gidds Oct 09 '18 at 14:53
  • 1
    It's not strictly the same, but you can also define it more directly. `val abc = {b:Int -> add(1, b) }`. Becomes specific to each usage, and convention to partially apply first, and type of parameter has to be explicitly defined. Style choice. – Mikezx6r Dec 19 '19 at 15:59
5

There is a very nice and light library to Kotlin: org.funktionale. In module funktionale-currying you'll find extenstion methods to lambdas: curried() and uncurried().

Example:

val add = { x: Int, y: Int -> x + y }.curried()
val add3 = add(3)

fun test() {
    println(add3(5)) // prints 8
}
Cililing
  • 4,303
  • 1
  • 17
  • 35
2

Option 1

It actually does, but you'll have to use signatures like

(T1) -> (T2) -> R

Here's an example of a curried comparator which allows partial application (the typealias added for readability, can just as well be rewritten w/o any):

typealias CurriedComparator<T> = (T) -> (T) -> Int

fun main() {
    val integers = mutableListOf(42, 0, 1, 3, 2)

    val naturalOrder: CurriedComparator<Int> = { left ->
        { right ->
            // JVM only, essentially the same as `left - right`
            Comparator.naturalOrder<Int>().compare(left, right)
        }
    }

    integers.sortWith { left, right ->
        // Note the partial application:
        naturalOrder(left)(right)
    }

    println(integers)
}

Having said the above, the curried operation for a two-argument function (from this answer) can be implemented like this:

val <T1, T2, R> ((T1, T2) -> R).curried: (T1) -> (T2) -> R
    get() = { arg0: T1 ->
        { arg1: T2 ->
            this(arg0, arg1)
        }
    }

Usage example:

val difference = { a: Int, b: Int -> a - b }.curried(11)(7)
println(difference) // 4

Option 2

You'll have to overload the invoke() operator, e. g.:

    val compareInts: (Int, Int) -> Int =
        { left, right ->
            left - right
        }

    operator fun <T1, T2, R> ((T1, T2) -> R).invoke(t1: T1): (T2) -> R =
        { t2 ->
            this(t1, t2)
        }

    // This is the "normal" invocation:
    val a: Int = compareInts(41, 42)
    // This is the partial application using the overloaded `invoke()` operator:
    val b: Int = compareInts(41)(42)

    check(a == b)

Limitations

The capability of partial application comes at a price. Once we follow the above path, we'll immediately lose access to:

  • Java interoperability,
  • default parameters, and, more importantly,
  • recursion (can still be implemented using the Y combinator).

Speaking of the recursion w/o the Y combinator, it's still possible, but (in the case of a 2-argument function) you'll have to rewrite

val comparator: (String) -> (String) -> Int = { /*...*/ }

as

fun comparator(left: String): (String) -> Int { /*...*/ }
Bass
  • 4,977
  • 2
  • 36
  • 82