Imagine you’re writing universal domain logic for your program. All I/O is provided via an interface (like UI, persistent storage, networking, etc.), you don’t have to think about it, and you almost don’t care about the platform, on which you run. Your program is fairly simple, only a few straightforward tasks. This chapter describes all essentials you need to write it. The foundational basics that you can build upon: data, processing primitives, and functions to reuse code.

Flow control

Let’s talk logic. The most basic thing you can do to process data or program a behavior is to execute different commands based on a decision. Then you may want to execute the same commands repeatedly over varying inputs – loop – or stop processing early – jump. Branching, loops, and jumps form the foundation of logic programming.

Note: Essentially, all logic can be boiled down to two primitives – branching and jumping, bundled together in different ways. However, when you think about designing an algorithm rather than taking the machine code level perspective, loops emerge as a distinct, fundamental concept. So they have a dedicated topic of their own.

Branching

If

if (true) {
  "Do something in a block".lowercase()
}

If-else:

if (true)
  "Then a single statement".lowercase()
else
  "Some other statement. These could also be blocks.".lowercase()

If-else can be used as an expression that returns a value that you can assign to a variable or return as a result of a function. There is no dedicated ternary operator per se.

var conditionalText = if (true) "ternary" else "operator"
conditionalText = if (false) {
  "Also do something else before. Last value is the result of the expression.".lowercase()
  "ternary"
} else {
  "Else is mandatory here".lowercase()
  "operator"
}

When

Switch looks like this:

val magicBag = mutableListOf<Any>()

when (conditionalText) {
  "ternary" -> magicBag.add(0, "ternary")
  "discombobulate" -> magicBag.add(0, "when doesn't need to be exhaustive as statement")
}

when (conditionalText) {
  "operator" -> {
    "Branches can be blocks".lowercase()
    magicBag.add(0, "These can contain more than one statement")
  }
  "other", "options", "same", "branch" -> 
    magicBag.add(0, "One-liners can be mixed with blocks at will")
  else -> {
    magicBag.add(0, "Default branch")
  }
}

Switches can also be used as expressions and assigned to variables:

conditionalText = when (conditionalText) {
  "switch" -> "when"
  "when" -> "when"
  else -> "when must be exhaustive as expression"
}

When doesn’t require a target to compare against. Its branches can be a loose collection of arbitrary conditions. And you can also assign to a scoped variable that can be used only inside when.

when {
  magicBag.isNotEmpty() -> magicBag.clear()
  else -> Unit // Do nothing
}

when (val size = magicBag.size) {
  in 1..<10 -> conditionalText = "Size is $size"
  !in 1..<10 -> conditionalText = "Size is too big"
}

[--]