Is it possible to write a "double" extension method?

Viewed 175

In Kotlin, it is possible to write

class A {
  fun B.foo()
}

and then e.g. write with (myA) { myB.foo() }.

Is it possible to write this as an extension method on A, instead? My use case is writing

with (java.math.RoundingMode.CEILING) { 1 / 2 }

which I would want to return 1, the point being that I want to add operator fun Int.div(Int) to RoundingMode.

3 Answers

No it's not possible. operator div is required to have Int as a receiver.

You can't add also RoundingMode as receiver, since there can only be single function receiver.

What you can do, though, is use Pair<RoundingMode, Int> as a receiver:

operator fun Pair<RoundingMode, Int>.div(i: Int): BigDecimal =
        BigDecimal.valueOf(second.toLong()).divide(BigDecimal.valueOf(i.toLong()), first)

with(RoundingMode.CEILING) {
    println((this to 1) / 2) // => 1
}

That's not possible, Int already has a div function, thus, if you decide to write an extension function div, you won't be able to apply it, because member functions win over extension functions.

You can write this though:

fun RoundingMode.div(x: Int, y: Int): Int {
    return if (this == RoundingMode.CEILING) {
        Math.ceil(x.toDouble() / y.toDouble()).toInt()
    } else {
        Math.floor(x.toDouble() / y.toDouble()).toInt()
    }
}

fun main(args: Array<String>) {
    with(java.math.RoundingMode.CEILING) {
        println(div(1,2))
    }
}

It's not possible for a couple of reasons:

  1. There's no "double extension functions" concept in Kotlin
  2. You can't override a method with extension functions, and operator div is already defined in Int

However you can workaround these issues with

  1. A context class and an extension lambda (e.g. block: ContextClass.() -> Unit)
  2. Infix functions (e.g. use 15 div 4 instead of 15 / 4)

See the example below:

class RoundingContext(private val roundingMode: RoundingMode) {
    infix fun Int.div(b: Int): Int {
        val x = this.toBigDecimal()
        val y = b.toBigDecimal()

        val res = x.divide(y, roundingMode)

        return res.toInt()
    }
}

fun <T> using(roundingMode: RoundingMode, block: RoundingContext.() -> T): T {
    return with(RoundingContext(roundingMode)) {
        block()
    }
}

// Test
fun main(args: Array<String>) {
    using(RoundingMode.FLOOR) {
        println(5 div 2) // 2
    }
    val x = using(RoundingMode.CEILING) {
        10 div 3
    }
    println(x) // 4
}

Hope it helps!

Related