michaelbull / kotlin-result

A multiplatform Result monad for modelling success or failure operations.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Kotlin with(receiver, block) already specifies contract

Munzey opened this issue · comments

But does the contract propagate through to the function in our library? Surely the compiler doesn't know how many times our function may call the block unless we explicitly tell them?

hmm maybe I'm misundestanding why this is being added or what these types of invoke contracts are useful for. From what I've read on the addition of contracts so far, its useful for two things:

  1. implying something is returned to make the compiler "smarter" by foregoing checks it would usually do and letting the dev specify the "contract"
  2. specifying how many times a lambda will be invoked, which if specified as exactly once allows the compiler to be smarter about letting one declare a val outside of the lambda, but only assigning it inside the lambda.

what is the reasoning or advantage to specifying it here?

https://kotlinlang.org/docs/reference/whatsnew13.html#contracts

Consider their example:

fun synchronize(lock: Any?, block: () -> Unit) {
    // It tells compiler:
    // "This function will invoke 'block' here and now, and exactly one time"
    contract { callsInPlace(block, EXACTLY_ONCE) }
}

fun foo() {
    val x: Int
    synchronize(lock) {
        x = 42 // Compiler knows that lambda passed to 'synchronize' is called
               // exactly once, so no reassignment is reported
    }
    println(x) // Compiler knows that lambda will be definitely called, performing
               // initialization, so 'x' is considered to be initialized here
}

Adapt it to the binding function:

inline fun <V, E> binding(crossinline block: ResultBinding<E>.() -> V): Result<V, E> {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    ...
}


fun foo() {
    val x: Int
    binding {
        val a: Int = funcA().bind()
        val b: Int = funcB().bind()

        x = a + b // Compiler knows that lambda passed to 'binding' is called
                  // exactly once, so no reassignment is reported
    }

    println(x) // Compiler knows that lambda will be definitely called, performing
               // initialization, so 'x' is considered to be initialized here
}

ok that makes sense to me now! I had to go try it out in the ide. thanks for the explanation :)