Do { scoped stuff } /* that is all */

I find myself writing code like

val (thing1, thing2) = mutableValueWithComponentGetters
/* do stuff that might mutate mutableValueWithComponentGetters */

val (thing1prime, thing2prime) = mutableValueWithComponentGetters
/* here I want to be careful not to use thing1 or thing2 since they might have changed above. */

In Java, I might wrap the first two lines in a block and then reuse the names (thing1, thing2) later on.
Kotlin obviously doesn’t allow raw blocks since that’d be ambiguous with lambda syntax.

What if the do keyword were reused to allow blocks for scoping purposes so that I could write

do {
  val (thing1, thing2) = mutableValueWithComponentGetters
  /* do stuff that might mutate mutableValueWithComponentGetters */
}

do {
  val (thing1, thing2) = mutableValueWithComponentGetters
  /* I don't have to be so careful here */
}

Often breaking things out into helper functions helps code readability, but helpers do require the reader to jump around so not always.

Current workarounds are

when { else -> {
  
} }

do {
  
} while (false);

The first is awkward looking, and the latter requires readers to scan forward for the while (false) to realize that the body is not actually a loop.

You could use the standard library scoping functions.
Here’s an example using run:

val mutableValueWithComponentGetters: MutableList<String> = mutableListOf("thing1", "thing2")

fun main() {
    //sampleStart
    run {
        val (thing1, thing2) = mutableValueWithComponentGetters
        println("do stuff that might mutate mutableValueWithComponentGetters")
    }

    run {
        val (thing1, thing2) = mutableValueWithComponentGetters
        println("I don't have to be so careful here")
    }
    //sampleEnd
}

Here’s an example using let:

val mutableValueWithComponentGetters: MutableList<String> = mutableListOf("thing1", "thing2")

fun main() {
    //sampleStart
    mutableValueWithComponentGetters.let { (thing1, thing2) ->
        // do stuff with thing1 and thing2 in scope.
    }
    //sampleEnd
}
3 Likes

Thanks. I didn’t know there was a standard way to do this.

In that case you should probably take a short look at kotlin’s scoping functions, they are super simple but powerful.
https://kotlinlang.org/docs/reference/scope-functions.html

1 Like

Thanks for pointing me at scoping functions.

Unless I’m missing something, there’s a case they don’t handle though.

var thingWithComponentGetters = ...;

if (thingWithComponentGetters != null) {
    // Here, thingWithComponentGetters is known to be non-null.

    run {
        // Here thingWithComponentGetters is not known to be non-null because the
        // compiler makes conservative assumptions about when nested functions might be called.
        val (thing1, thing2) = thingWithComponentGetters;  // ERROR: can't unpack a nullable
        if (complexExpression(thing1, thing2)) {
            thingWithComponentGetters = thingWithComponentGetters.derive(...);
        }
    }

    run {
        // Nor here.
        val (thing1, thing2) = thingWithComponentGetters;
        ...
    }
}

It’s possible since (presumably) run is an inline function, that a sufficiently-smart-compiler™ could realize post-inlining that run's input is affine (called at most once and before the function to which it’s passed exits) but AFAICT, that’s not done yet.

One workaround would be to use a return value to move the assignment out of the lambda.

// Globally
fun <IN, OUT> runUsing(x: IN, f: (x: IN) -> OUT): OUT = f(x)

 // Locally
var thingWithComponentGetters = ...;

if (thingWithComponentGetters != null) {
    // Here, thingWithComponentGetters is known to be non-null.

    thingWithComponentGetters = runUsing(thingWithComponentGetters) { thingWithComponentGetters ->
        // Here thingWithComponentGetters is known to be non-null by type inference.  🎉
        val (thing1, thing2) = thingWithComponentGetters;
        if (complexExpression(thing1, thing2)) {
            thingWithComponentGetters = thingWithComponentGetters.derive(...);
        }
    }

    thingWithComponentGetters = runUsing(thingWithComponentGetters) { thingWithComponentGetters ->
        // Since OUT is inferred separately from IN, both type guards from the `if` and from the body
        // of the first call to runUsing should be apparent here.
        val (thing1, thing2) = thingWithComponentGetters;
        ...
    }
} 

But, thingWithComponentGetters = runUsing(thingWithComponentGetters) { thingWithComponentGetters -> is super ugly and triggers (correctly) IDEA’s variable masking warnings.

I don’t want to introduce different names for thingWithComponentGetters in two different scopes because that reintroduces the thing1 vs thing1prime problem that I was initially trying to avoid; having two different names for a single conceptual entity makes for confusing code.

Yeah it is. This is a part of kotlin since 1.3, I belive and it works fine for me

data class Foo(val a: Int = 1, val b: Int = 2)

fun main() {
//sampleStart
	var thingWithComponentGetters: Foo? = Foo()

	if (thingWithComponentGetters != null) {
		run {
    	    val (thing1, thing2) = thingWithComponentGetters;  
            println(thing1)
            println(thing2)
		}
    }
//sampleEnd
}

This is done with Contracts, which are fully suported on a bytecode level but the api is still experimental. Just google them, there are plenty of blog posts about them.

1 Like

Note that Kotlin has nested functions so you can declare them right there within the the function. A well named function can do wonders for readability. See https://kotlinlang.org/docs/reference/functions.html#local-functions

But you also mentioned mutable variables which are usually a bad idea, but hard to speak to that without a concrete example.

3 Likes

Odd. Below is a self-contained example that fails for me with

Smart cast to ‘Bar’ is impossible, because ‘foo’ is a local variable that is captured by a changing closure

at the lines inside run{...} marked with :x: but not at the lines with :heavy_check_mark: in a version that does not use run{...}.


sealed class Foo(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y

    abstract fun derive(): Foo
}

class Bar(x: Int, y: Int) : Foo(x, y) { override fun derive() = Baz(x, y) }
class Baz(x: Int, y: Int) : Foo(x, y) { override fun derive() = Bar(y, x) }

fun f(a: Int, b: Int) {
    var foo: Foo? = Bar(a, b)
    if (foo is Bar) {
        val (x, y) = foo        // ✔️
        if (x < y) {
            foo = foo.derive()  // ✔️
        }

        val (xp, yp) = foo      // ✔️
        if (xp > yp) {
            foo = foo.derive()  // ✔️
        }
    }
}

fun g(a: Int, b: Int) {
    var foo: Foo? = Bar(a, b)
    if (foo is Bar) {
        run {
            val (x, y) = foo        // ❌
            if (x < y) {
                foo = foo.derive()  // ❌
            }
        }

        run {
            val (x, y) = foo        // ❌
            if (x > y) {
                foo = foo.derive()  // ❌
            }
        }
    }
}

I’m pretty sure I’m using Kotlin ≥ 1.3.

My build.gradle says

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'

but I’m unsure how kotlin/gradle plugin versions relate to language versions so I looked at the MPP docs and added

kotlin.sourceSets.all {
    languageSettings.languageVersion = '1.3'
    languageSettings.apiVersion = '1.3'
}

My IDEA settings show “Latest stable (1.3)”

You’re right. Your code fails for me as well. I can’t really explain why though. It looks like somethings up with the componentN functions messing this up.
It feels like a bug to me, but maybe there is something going on here that I don’t understand.

1 Like

@Wasabi375, thanks for your thoughts. Filed as KT-35800 so the jetbrains can think through this.

An alternative to run { } in this case may also be:

{
    // Your code
}()

Essentially creating a mini function and executing it

1 Like

I think it happens because compiler does not see run inlining for some reason. Without inline it is rather obvious. You capture the state of external scope when you invoke run and foo is a variable, so it is possible that somewone could assign it to null in process. First case works only because compiler is smart and it sees that foo is local and could not be reassigned between initial assignment and usage.

I expect it’s because JetBrains doesn’t want your smart casts to break if the implementation (not the signature) of run is changed, so in trying to prove that foo is non-null, it doesn’t consider the implementation.

A different implementation of run could execute the block twice or do something else first that causes foo==null.

@mtimmerm IIUC, this previous comment says that it is supposed to since Kotlin 1.3. Do you know of any docs that would indicate that’s wrong?

Nope, but it’s not the same use case, since in that previous comment, the variable in question is not changed inside the closure. It wouldn’t matter if the lambda was called twice or not at all, and the variable could not be changed by run no matter what the implementation was.

1 Like

Fair point. I had interpreted that as “since Kotlin 1.3, flow-sensitive checks of bodies of inline lambda arguments to inline functions is delayed until after inlining happens” in which case the two should be equivalent, but I suppose suppose some checks could be delayed and others not.