Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fall through in Kotlin: Switch/When without break [duplicate]

Pattern matching in Kotlin is nice and the fact it does not execute the next pattern match is good in 90% of use cases.

In Android, when database is updated, we use Java switch property to go on next case if we do not put a break to have code looking like that:

switch (oldVersion) {
    case 1: upgradeFromV1();
    case 2: upgradeFromV2(); 
    case 3: upgradeFromV3();
}

So if someone has an app with version 1 of the DB and missed the app version with DB v2, he will get all the needed upgrade code executed.

Converted to Kotlin, we get a mess like:

when (oldVersion) {
    1 -> {
        upgradeFromV1()
        upgradeFromV2()
        upgradeFromV3()
    }
    2 -> {
        upgradeFromV2()
        upgradeFromV3()
    }
    3 -> {
        upgradeFromV3()
    }
}

Here we have only 3 versions, imagine when DB reaches version 19.

Anyway to makes when acting in the same way then switch? I tried to continue without luck.

like image 619
Geob-o-matic Avatar asked Jun 14 '15 17:06

Geob-o-matic


People also ask

How does Kotlin's when differ from Java's switch?

Kotlin has a simple and concise syntax. Codes written in this language have better readability than equivalent codes written in Java. Below are three different ways to write a simple function that adds two numbers together. The first 2 examples are written in Kotlin, while the third is written in Java.

Does Kotlin need break?

In Kotlin, if a matching case is found, only the code in the respective case block is executed, and execution continues with the next statement after the when block. This essentially means that we don't need break statements at the end of each case block.

How do I choose between switches and when in Kotlin?

In Java we use switch but in Kotlin, that switch gets converted to when. when is also used for conditional representation but it does the things in a very smarter and easier way. Whenever you are having a number of possibilities then you can use when in your code.

Can when statement in Kotlin be used without passing any argument?

it can be used without an argument.


5 Answers

Simple but wordy solution is:

if (oldVersion <= 1) upgradeFromV1()
if (oldVersion <= 2) upgradeFromV2()
if (oldVersion <= 3) upgradeFromV3()

Another possible solution with function references:

fun upgradeFromV0() {}
fun upgradeFromV1() {}
fun upgradeFromV2() {}
fun upgradeFromV3() {}

val upgrades = arrayOf(::upgradeFromV0, ::upgradeFromV1, ::upgradeFromV2, ::upgradeFromV3)

fun upgradeFrom(oldVersion: Int) {
    for (i in oldVersion..upgrades.lastIndex) {
        upgrades[i]()
    }
}
like image 62
bashor Avatar answered Oct 17 '22 04:10

bashor


edit: Original response below. Here's what I'm currently doing:

fun upgrade() {
    fun upgradeFromV1() { /* Do stuff */ }
    fun upgradeFromV3() { /* Do stuff */ }

    tailrec fun upgradeFrom(version: Int): Unit = when (version) {
        LATEST_VERSION -> {
            Config.version = version
        } 1 -> {
            upgradeFromV1()
            upgradeFrom(2)
        } in 2..3 -> {
            upgradeFromV3()
            upgradeFrom(4)
        } else -> {
            Log("Uncaught upgrade from $version")
            upgradeFrom(version+1)
    }

    upgradeFrom(Config.version)
}

Here's a variation on the answer @C.A.B. gave:

fun upgrade(oldVersion: Int) {
    when (oldVersion) {
        latestVersion -> return
        1 -> upgradeFromV1()
        2 -> upgradeFromV2()
        3 -> upgradeFromV3()
    }
    upgrade(oldVersion + 1)
}
like image 26
Julian Delphiki Avatar answered Oct 17 '22 05:10

Julian Delphiki


How about this:

fun upgradeFromV3() {/* some code */}
fun upgradeFromV2() {/* some code */ upgradeFromV3()}
fun upgradeFromV1() {/* some code */ upgradeFromV2()}
fun upgradeFromV0() {/* some code */ upgradeFromV1()}

fun upgrade(oldVersion: Int) {
    when (oldVersion) {
        1 -> upgradeFromV1()
        2 -> upgradeFromV2()
        3 -> upgradeFromV3()
    }
}

Added:

I like the idea by @lukle to define the upgrade path as a list. This allows to define different upgrade paths for different initial stage. For example:

  1. Simple fast path from released version to the latest released version
  2. Catch-up path from hot-fix version (could be few in a row), which should not be applied when going from previous full version to the next full version

For that we need to know from which elements of the list to apply.

fun <Vs, V> Pair<Vs, V>.apply(upgrade: () -> Unit): (V) -> V {
    return { current: V ->
        if (first == current) {
            upgrade()
            second
        } else {
            current
        }
    }
}

val upgradePath = listOf(
        (0 to 10).apply  { /* do something */ },
        (5 to 15).apply  { /* do something */ },
        (10 to 20).apply { /* do something */ },
        (15 to 20).apply { /* do something */ },
        (20 to 30).apply { /* do something */ },
        (30 to 40).apply { /* do something */ }
)

fun upgrade(oldVersion: Int) {
    var current = oldVersion
    upgradePath.forEach { current = it(current) }
}

In this code Vs could be the same as V or some kind of collection of V values with overridden equals(other: Any?): Boolean method.

like image 35
C.A.B. Avatar answered Oct 17 '22 05:10

C.A.B.


You can just use for loop with when.

for (version in oldVersion..newVersion) when (version) {
    1 -> upgradeFromV1()
    2 -> upgradeFromV2()
    3 -> upgradeFromV3()
}
like image 11
John Avatar answered Oct 17 '22 04:10

John


It is absolutly possible quote from official reference : Control Flow: if, when, for, while

If many cases should be handled in the same way, the branch conditions may be combined with a comma:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

So if same condition list is short, then you can list them separating by coma, or use ranges like condition in 1..10 as stated in other answers

like image 2
Yarh Avatar answered Oct 17 '22 04:10

Yarh