BenWoodworth / Parameterize

Kotlin syntax for clean parameterized code
Apache License 2.0
36 stars 3 forks source link

Implement `parameterize` as multi-shot coroutine #34

Open BenWoodworth opened 2 weeks ago

BenWoodworth commented 2 weeks ago

This is not a planned feature, at least not yet, but this issue tracks how parameterize could work if it were implemented as a suspend block that resumes from each parameter instead of re-running the whole block

It could be implemented as a suspend point when declaring a parameter:

suspend operator fun Parameter.provideDelegate()

A parameter will be constructed only once (instead of every iteration). After that, there will be a suspend point when provideDelegate is called on it, and having the listed/computed arguments locked in. From there, parameterize's suspend block will resume from there once for each of the arguments as a multi-shot coroutine.

Benefits

Problems/Blockers

Implementation

Multi-shot coroutines should be possible as long as a coroutine continuation can be cloned, that way it can be resumed more than once.

For each platform:

BenWoodworth commented 1 week ago

There may be some fundamental issues with this as well, since it'd be breaking assumptions that Kotlin makes about code.

For example:

suspend fun multiResume() {}

suspend fun x() {
    val a: Int

    multiResume()

    a = 4
    a = 5 // ERROR: 'val' cannot be reassigned
}

Here Kotlin is fine with the first assignment since it assumes that the continuation at multiResume() will be resumed at most once. But, of course, with a multi-shot coroutine like what this issue's proposing, that's not the case.

Testing on JVM I'm finding that the code does run without any issue, replacing the value of 4 with 5. There are some subtle inconsistencies that can arise though:

fun test() {
    val a: Int

    a = 4
    val printCaptured = { println(a) }

    @Suppress("VAL_REASSIGNMENT")
    a = 5

    printCaptured()
    println(a)
}

In this example, a is captured in the lambda, and since it's a val and won't change, the constant value 4 is captured, instead of a reference that will change to 5 when a is updated. So in this code, "4 5" is printed. But, if a is changed to a var, a reference to a's value is captured, and the lambda will always print the actual current value of a, meaning that var a results in "5 5" being printed.

While this isn't necessarily a problem, and might even be desirable, it might hint towards a bigger issue when abusing coroutines like this.