Everything else is just a tool to model your domain in a way that’s easier to read and reason about, and to produce much shorter code. Interfaces define module boundaries and can be mocked to enable simpler testing. Through classes, objects, and data structures, you can model your domain entities. Enums make a list of limited options crystal clear. Extensions allow you to add functionality to existing types, without having access to their original source code, giving advantage of better logic structure. With generics, you can write universal code, promoting reuse. And overloaded operators give that extra edge to common operations that benefit from succinct, crisp syntax.

Object types (classes)

Custom types that focus on behavior. Object types allow you to model your domain, so you can reason about it more easily. You can give things familiar names, group properties and operations, define relationships, and form as many layers of abstraction as you like. These are subjective representations of the author’s imagination. Oftentimes quite useful, as we are used to think in models of the world.

Anonymous objects

You don’t have to create a new type for a one-off implementation of an interface. You can use anonymous objects.

interface TextArtGenerator {
  fun draw(): String
}

fun appendArt(message: String, generator: TextArtGenerator): String {
  return """
  $message
  
  ${generator.draw()}
  """
}

appendArt("Hello!", object: TextArtGenerator {
  override fun draw() = "¯\\_(ツ)_/¯"
})

Also, for local encapsulation, for example:

class Parser(val input: String) {
  var cursor = 0

  fun extractBetween(start: String, end: String): List<String> {
    val result = mutableListOf<String>()

    do {
      if (chunkUntil(start) == null) break
      val content = chunkUntil(end) ?: break
      result.add(content.text)
    } while (content.position < input.length)

    return result
  }

  private fun chunkUntil(token: String) = input
    .indexOf(token, cursor)
    .let {
      if (it < 0) null else object {
        val text = input.substring(cursor, it)
        val position = it

        fun updateCursor() {
          cursor = position + token.length
        }
      }
    }
    .also {
      if (it == null)
        cursor = input.length
      else
        it.updateCursor()
    }
}

[--]