Geob-o-matic
Geob-o-matic

Reputation: 6079

Kotlin 'when' statement vs Java 'switch'

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.

Upvotes: 77

Views: 51091

Answers (12)

Hukum
Hukum

Reputation: 47

val oldVersion = 6 val newVersion = 10

for (version in oldVersion until newVersion) {
    when (version) {
        1 -> upgradeFromV1()
        2 -> upgradeFromV2()
        3 -> upgradeFromV3()
        4 -> upgradeFromV4()
        5 -> upgradeFromV5()
        6 -> upgradeFromV6()
        7 -> upgradeFromV7()
        8 -> upgradeFromV8()
        9 -> upgradeFromV9()
    }
    println("~~~")
}

Upvotes: -2

Emre
Emre

Reputation: 1265

If you didn't care about the order in which you run these functions, you could make your own pseudo-switch, something like:

function PretendSwitch() {
  if(oldVersion>3) return
  upgradeFromV3();
  if(oldVersion==3) return
  upgradeFromV2()
  if(oldVersion==2) return
  upgradeFromV1()
  if(oldVersion==1) return
}

Nothing is going to be as clean as using a switch. Unfortunately, Kotlin lacks a switch statement so there is no way do perform this elegantly.

Upvotes: 0

John
John

Reputation: 1543

You can just use for loop with when.

for (version in oldVersion..newVersion) when (version) {
    1 -> upgradeFromV1()
    2 -> upgradeFromV2()
    3 -> upgradeFromV3()
}

Upvotes: 12

Victor Yevitchenko
Victor Yevitchenko

Reputation: 59

What about Kotlin DSL for custom implementation? Something like this approach:

class SwitchTest {

    @Test
    fun switchTest() {

        switch {
            case(true) {
                println("case 1")
            }
            case(true) {
                println("case 2")
            }
            case(false) {
                println("case 3")
            }
            caseBreak(true) {
                println("case 4")
            }
            case(true) {
                println("case 5")
            }
//          default { //TODO implement
//
//          }
        }
    }
}

class Switch {
    private var wasBroken: Boolean = false

    fun case(condition: Boolean = false, block: () -> Unit) {
        if (wasBroken) return
        if (condition)
            block()
    }

    fun caseBreak(condition: Boolean = false, block: () -> Unit) {
        if (condition) {
            block()
            wasBroken = true
        }
    }
}

fun switch(block: Switch.() -> Unit): Switch {
    val switch = Switch()
    switch.block()
    return switch
}

It prints: case 1 case 2 case 4 UPD: Some refactorings and output example.

Upvotes: 3

arslancharyev31
arslancharyev31

Reputation: 1971

Another variation of OP's answer:

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    when (oldVersion) {
        newVersion -> return
        1 -> TODO("upgrade from v1 to v2")
        2 -> TODO("upgrade from v2 to v3")
    }
    oldVersion++
    onUpgrade(db, oldVersion, newVersion)
}

Upvotes: 2

C.A.B.
C.A.B.

Reputation: 583

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.

Upvotes: 14

Yarh
Yarh

Reputation: 4607

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

Upvotes: 2

Bharat Vasoya
Bharat Vasoya

Reputation: 361

Kotlin works with a different flow control called when.

Your code, using it when, can be that way.

Obviously the code could be different, but I understand that your question is only about the use of switch.

fun main(args: Array<String>) {
val month = 8

val monthString = when(month) {
    1 -> "Janeiro"
    2 -> "February"
    3 -> "March"
    4 -> "April"
    5 -> "May"
    6 -> "June"
    7 -> "July"
    8 -> "August"
    9 -> "September"
    12 -> "October"
    11 -> "November"
    10 -> "December"
    else -> "Invalid month"      
}

println(monthString);
}

Upvotes: -4

lukDeveloper
lukDeveloper

Reputation: 3

val orders = arrayListOf(
            { upgradeFromV1()},
            { upgradeFromV2()},
            { upgradeFromV3()}
)

orders.drop(oldVersion).forEach { it() }

Upvotes: -1

Julian Delphiki
Julian Delphiki

Reputation: 403

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)
}

Upvotes: 18

Lukas Lechner
Lukas Lechner

Reputation: 8181

Here is a mix of the two answers from bashor, with a little bit of functional sugar:

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

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

fun upgradeFrom(oldVersion: Int) {
    upgrades.filterIndexed { index, kFunction0 -> oldVersion <= index }
            .forEach { it() }
}

Upvotes: 0

bashor
bashor

Reputation: 8453

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]()
    }
}

Upvotes: 77

Related Questions