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.

Functions

Functions are an elegant way to reuse parts of your program. They help to split tasks into smaller tasks and allow zooming between levels of abstraction. For example, you don’t have to write code for efficient sorting every time you need to sort your collection. You can reuse an already written routine. Functions define a clear scope, inputs and outputs. Without them, programs would be very long and tedious.

You can even think of your whole program as functions composing into each other, according to an ingenious paradigm called functional programming.

Inlining

Inline functions are an optimization. They are sort of macros that unfold their code at the places they are used. Since they are essentially templates to be copy-pasted, they cannot be referenced for later use or stored in a variable.

To opt out a lambda inside an inlined function from inlining, noinline modifier can be used.

inline fun joinWithSideEffect(
  source: Iterable<String>,
  separator: (Pair<String, Int>, Pair<String, Int>) -> String,
  noinline sideEffect: (String) -> Unit
): String {
  if (source.count() < 2) {
    return source.firstOrNull()?.also { sideEffect(it) } ?: ""
  }

  return source
    .windowed(2)
    .foldIndexed("") { index, acc, window ->
      var chunk = ""
      if (index == 0) {
        chunk = window[0]
        sideEffect(chunk)
      }

      chunk += separator(window[0] to index, window[1] to (index + 1))
      chunk += window[1]
      (acc + chunk).also(sideEffect)
    }
}

joinWithSideEffect(listOf("one", "two"), separator = { _, _ -> ", " }) {}

Inlined functions with inlined lambdas can behave unexpectedly with regular returns. Such returns are non-local and exit the encompassing scope! You can prohibit such behavior with crossinline modifier.

fun scopedJoin(): String {
  return joinWithSideEffect(
    listOf("one", "two"),
    // Non-local returns are allowed inside inlined lambdas. 
    // They return from the encompassing scope!
    separator = { _, _ -> return "Surprise!" }
  ) {}
}

inline fun joinWithoutSurprise(
  source: Iterable<String>,
  crossinline separator: (Pair<String, Int>, Pair<String, Int>) -> String,
  noinline sideEffect: (String) -> Unit
) = joinWithSideEffect(source, separator, sideEffect)

[--]