infix and Operator Functions in DSLs

Readable APIs

infix functions can be called without dot and parentheses (a to b). operator functions let you use symbols (+, [], in, ..). Used carefully, they make test assertions, money and date code read like English.

Example

data class Money(val paise: Long) {
    operator fun plus(o: Money) = Money(paise + o.paise)
    operator fun times(n: Int) = Money(paise * n)
    operator fun compareTo(o: Money) = paise.compareTo(o.paise)
    override fun toString() = "Rs ${paise / 100}"
}
val Int.rupees get() = Money(this * 100L)

infix fun <T> T.shouldBe(expected: T) {
    println(if (this == expected) "PASS: $this" else "FAIL: expected $expected but was $this")
}

class Cart {
    private val items = linkedMapOf<String, Money>()
    operator fun set(name: String, price: Money) { items[name] = price }
    operator fun get(name: String) = items[name]
    operator fun contains(name: String) = name in items
    val total get() = items.values.fold(0.rupees) { a, b -> a + b }
}

fun main() {
    val cart = Cart()
    cart["Notebook"] = 60.rupees * 3
    cart["Pen"] = 20.rupees
    println("Total: ${cart.total}")
    ("Pen" in cart) shouldBe true
    cart["Notebook"] shouldBe 180.rupees
    (cart.total > 150.rupees) shouldBe true
}

Output

Total: Rs 200
PASS: true
PASS: Rs 180
PASS: true

DSLs You Already Use

// Gradle Kotlin DSL
dependencies {
    implementation("io.ktor:ktor-server-core:3.0.0")
}

// Ktor routing
routing {
    get("/products/{id}") { call.respond(repo.find(call.parameters["id"]!!)) }
}

// Jetpack Compose is a DSL built on @Composable lambdas
Column { Text("Hello"); Button(onClick = {}) { Text("OK") } }