# Kotlin Language

# Overview

Kotlin is a modern, general-purpose programming language designed by JetBrains. Originally designed as a drop-in replacement for Java, Kotlin has proven to be a versatile, extensible language that is well-suited for multiplatform and full-stack development. We will use it to build desktop and mobile applications, and backend services.

# Compiling

The Kotlin programming language includes a set of compilers that can be used to target different platforms.

flowchart LR
    kotlin([Kotlin Code])
    jvm([Kotlin/JVM])
    native([Kotlin/Native])
    js([Kotlin/JS])
    wasm([Kotlin/WASM])
    kotlin --> jvm
    kotlin --> native
    kotlin --> js
    kotlin --> wasm
  • Kotlin/JVM compiles Kotlin to JVM bytecode, which can be interpreted on a Java virtual machine. This supports running Kotlin code anywhere a JVM is supported (typically desktop, server).
  • Kotlin/Native compiles Kotlin to native executables. This provides support for iOS and other targets.
  • Kotlin/JS transpiles Kotlin to JavaScript. The current implementation targets ECMAScript 5.1.
  • Kotlin/WASM adds support for WASM virtual machine standard, allowing Kotlin to run on the web.

Kotlin/JVM and Kotlin/Native for Android are the best-supported targets at this time, and what we will focus on in this course.

# Execution

There are three primary ways of executing Kotlin code:

# 1. REPL

REPL is a paradigm where you type and submit expressions to the compiler one line-at-a-time. It’s commonly used with dynamic languages for debugging, or checking short expressions. It’s not intended as a means of writing full applications!

$ kotlin
Welcome to Kotlin version 1.6.10 (JRE 17.0.2+8-86)
Type :help for help, :quit for quit
>>> val message="Hello Kotlin!"
>>> println(message)
Hello Kotlin!

# 2. KotlinScript

KotlinScript is Kotlin code in a script file that we can execute from our shell. This makes Kotlin an interesting alternative to a language like Python for shell scripting.

$ cat hello.kts
#!/usr/bin/env kotlin
val message="Hello Kotlin!"
println(message)

$ ./hello.kts
Hello Kotlin!

Kotlin compiles scripts in the background before executing them, so there's a delay before it executes (ed note: I fully expect that later versions of Kotlin will allow caching the compilation results to speedup script execution time).

This is a great way to test functionality, but not a straight-up replacement for shell scripts, due to runtime compilation costs.

# 3. Applications

Kotlin's applications are fully-functional, and can be compiled to JVM or native code. Kotlin's application code looks a little like C, or Java. Here’s the world’s simplest Kotlin program, consisting of a single main method.

fun main() {
	val message="Hello Kotlin!"
	println(message)
}

To compile from the command-line, we can use the Kotlin compiler, kotlinc. By default, it takes Kotlin source files (.kt) and compiles them into corresponding class files (.class) that can be executed on the JVM.

$ kotlinc Hello.kt

$ ls
Hello.kt HelloKt.class

$ kotlin HelloKt
Hello Kotlin!

Notice that the compiled class is named slightly differently than the source file. If your code isn't contained in a class, Kotlin wraps it in an artificial class so that the JVM (which requires a class) can load it properly. Later when we use classes, this won't be necessary.

This example compiles Hello.kt into Hello.jar and then executes it:

$ kotlinc Hello.kt -include-runtime -d Hello.jar

$ ls
Hello.jar Hello.kt

$ java -jar Hello.jar
Hello Kotlin!

# Packages

Packages are meant to be a collection of related classes. e.g. graphics classes. Packages are primarily a mechanism for managing dependencies between parts of an application, and encourage clear separation of concerns. They are conceptually similar to namepsaces in C++.

Use the package declaration at the top of a source file to assign a file to a namespace. Classes or modules in the same package have full visibility to each other.

For example, in the file below, contents are contained in the ca.uwaterloo.cs346 package. The full name of the class includes the package and class name: ca.uwaterloo.cs346.ErrorMessage . If you were referring to it from a different package, you would need to use this fully qualified name.

package ca.uwaterloo.cs346

class ErrorMessage(val msg:String) {
  fun print() {
    println(msg)
  }
}

fun main() {
  val error = ErrorMessage("testing an error condition")
  error.print()
}

To use a class in a different namespace, we need to import the related class by using the import keyword. In the example below, we import our ErrorMessage class into a different namespace so that we can instantiate and use it.

import ca.uwaterloo.cs346.ErrorMessage

class Logger {
  val error = ErrorMessage()
  error.printMessage()
}

# JAR Files

Class files by themselves are difficult to distribute. The Java platform includes the jar utility, used to create a single archive file from your classes.

A JAR file is a standard mechanism in the Java ecosystem for distributing applications. It's effectively just a compressed file (just like a ZIP file) which has a specific structure and contents. Most distribution mechanisms expect us to create a JAR file first.

This example compiles Hello.kt, and packages the output in a Hello.jar file.

$ kotlinc Hello.kt -include-runtime -d Hello.jar

$ ls
Hello.jar Hello.kt

The -d option tells the compiler to package all of the required classes into our jar file. The -include-runtime flag tells it to also include the Kotlin runtime classes. These classes are needed for all Kotlin applications, and they're small, so you should always include them in your distribution (if you fail to include them, you app won't run unless your user has Kotlin installed).

To run from a jar file, use the java command.

$ java -jar Hello.jar
Hello Kotlin!

In the same way that the Java compiler compiles Java code into IR code that will run on the JVM, the Kotlin compiler also compiles Kotlin code into compatible IR code. The java command will execute any IR code, regardless of which programming language and compiler was used to produce it.

Our JAR file from above looks like this if you uncompress it:

$ unzip Hello.jar -d contents
Archive:  Hello.jar
  inflating: contents/META-INF/MANIFEST.MF  
  inflating: contents/HelloKt.class  
  inflating: contents/META-INF/main.kotlin_module  
  inflating: contents/kotlin/collections/ArraysUtilJVM.class 
  ...

$ tree -L 2 contents/
.
├── META-INF
│   ├── MANIFEST.MF
│   ├── main.kotlin_module
│   └── versions
└── kotlin
    ├── ArrayIntrinsicsKt.class
    ├── BuilderInference.class
    ├── DeepRecursiveFunction.class
    ├── DeepRecursiveKt.class
    ├── DeepRecursiveScope.class
    ...

The JAR file contains these main features:

  • HelloKt.class – a class wrapper generated by the compiler
  • META-INF/MANIFEST.MF – a file containing metadata.
  • kotlin/ – Kotlin runtime classes not included in the JDK.

The MANIFEST.MF file is autogenerated by the compiler, and included in the JAR file. It tells the runtime which main method to execute. e.g. HelloKt.main().

$ cat contents/META-INF/MANIFEST.MF 
Manifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: HelloKt

# Libraries

Kotlin has full access to its own class libraries, plus any others that are imported (typically from JAR files). Kotlin is 100% compatible with Java libraries, and makes extensive use of Java libraries when possible. For example, Kotlin collection classes actually use some of the underlying Java collection libraries!

In this section, we'll discuss how to use existing libraries in your code.

# Kotlin Standard Library

The Kotlin Standard Library is included with the Kotlin language, and contained in the kotlin package. This is automatically imported and does not need to be specified in an import statement.

Some of the features that will be discussed below are actually part of the standard library (and not part of the core language). This includes essential classes, such as:

  • Higher-order scope functions that implement idiomatic patterns (let, apply, use, etc).
  • Extension functions for collections (eager) and sequences (lazy).
  • Various utilities for working with strings and char sequences.
  • Extensions for JDK classes making it convenient to work with files, IO, and threading.

# Java Standard Library

Kotlin is completely 100% interoperable with Java, so all of the classes available in Java/JVM can also be imported and used in Kotlin.

// import all classes in the java.io package
// this allows us to refer to any of those classes in the current namespace
import java.io.*

// we can also just import a single class
// this allows us to refer to just the ListView class in code
import javafx.scene.control.ListView

// Kotlin code calling Java IO libraries
import java.io.FileReader
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.FileWriter
import java.io.BufferedWriter

if (writer != null) {
	writer.write(
  	row.toString() + delimiter +
    	s + row + delimiter +
      pi + endl
  )

Importing a class requires your compiler to locate the file containing these classes! The Kotlin Standard Library can always be referenced by the compiler, and as long as you're compiling to the JVM, the Java class libraries will also be made available.

# Third-Party Libraries

To use any other Java or Kotlin library, you will to add it to your Gradle dependencies in your build.gradle.kts. This makes them available to import in your source code

// build.gradle.kts

dependencies {
  implementation("com.github.ajalt.clikt:clikt:4.2.2")
  testImplementation(kotlin("test"))
}

# Kotlin Basics

Kotlin is a class-based object-oriented language, which also supports a functional paradigm. Syntactically, it is similar to other C-style programming languages.

# Types

The type system of a programming language is the set of rules that are applied to expressions in that language. We differentiate different type systems by the types of rules that they apply:

  • Strong typing: The language has strict typing rules, which are typically enforced at compile-time. The exact type of variable must be declared or fixed before the variable is used. This has the advantage of catching many types of errors at compile-time (e.g. type-mismatch).
  • Weak typing: These languages have looser typing rules, and will often attempt to infer types based on runtime usage. This means that some categories of errors are only caught at runtime.

Kotlin is a strongly typed language, where variables need to be declared before they are used. Kotlin also supports type inference. If a type isn’t provided, Kotlin will infer the type at compile time (similar to ‘auto‘ in C++). The compiler is strict about this: if the type cannot be inferred at compile-time, an error will be thrown.

# Variables

Kotlin uses the var keyword to indicate a variable, and Kotlin expects variables to be declared before use. Types are always placed to the right of the variable name. Types can be declared explicitly, but will be inferred if the type isn't provided.

fun main() {
  var a:Int = 10
  var b:String = "Jeff"
  var c:Boolean = false

  var d = "abc"	   // inferred as a String
  var e = 5        // inferred as Int
  var f = 1.5      // inferred as Float 
}

All standard data-types are supported, and unlike Java, all types are objects with properties and behaviours. This means that your variables are objects with methods! E.g. "10".toInt() does what you would expect.

# Supported Types

Integers

Type Size (bits) Min value Max value
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-2 31) 2,147,483,647 (2 31- 1)
Long 64 -9,223,372,036,854,775,808 (-2 63) 9,223,372,036,854,775,807 (2 63- 1)

Floating Point Types

Type Size (bits) Significant bits Exponent bits Decimal digits
Float 32 24 8 6-7
Double 64 53 11 15-16

Boolean

The type Boolean represents boolean objects that can have two values: true and false. Boolean has a nullable counterpart Boolean? that also has the null value.

Built-in operations on booleans include:

  • || – disjunction (logical OR)
  • && – conjunction (logical AND)
  • !- negation (logical NOT)
  • || and && work lazily.

Strings

Strings are often a more complex data type to work with. In Kotlin, they are represented by the String type, and are immutable. Elements of a string are characters that can be accessed by the indexing operation: s[i], and you can iterate over a string with a for-loop:

fun main() {
  val str = "Sam"
  for (c in str) { 
      println(c) 
  } 
}

click to run
https://pl.kotl.in/dXJQxARa0

You can concatenate strings using the + operator. This also works for concatenating strings with values of other types, as long as the first element in the expression is a string (in which case the other element will be case to a String automatically):

fun main() {
  val s = "abc" + 1
  println(s + "def")
}

click to run
https://pl.kotl.in/neET5SnVs

Kotlin supports the use of string templates, so we can perform variable substitution directly in strings. It’s a minor but incredibly useful feature that replaces the need to concatenate and build up strings to display them.

fun main() {
  println("> Kotlin ${KotlinVersion.CURRENT}") 

  val str = "abc" 
  println("$str.length is ${str.length}") 

  var n = 5 
  println("n is ${if(n > 0) "positive" else "negative"}")
}

click to run
https://pl.kotl.in/TDKMniPLe

# is and !is operator

To perform a runtime check whether an object conforms to a given type, use the is operator or its negated form !is:

fun main() {
  val obj = "abc"

  if (obj is String) {
    print("String of length ${obj.length}")
  } else {
    print("Not a String")
  }
}

click to run
https://pl.kotl.in/hq_-JKPuN

In most cases, you don't need to use explicit cast operators in Kotlin because the compiler tracks the is checks and explicit casts for immutable values and inserts (safe) casts automatically when needed:

fun main() {
  val x = "abc"
  if (x !is String) return
  println("x=${x.length}") // x is automatically cast to String

  val y = "defghi"
  // y is automatically cast to string on the right-hand side of `||`
  if (y !is String || y.length == 0) return
  println("y=${y.length}") // y must be a string with length > 0
}

click to run
https://pl.kotl.in/jbUgWu-I2

# Immutability

Kotlin supports the use of immutable variables and data structures [mutable means that it can be changed; immutable structures cannot be changed after they are initialized]. This follows best-practices in other languages (e.g. use of final in Java, const in C++), where we use immutable structures to avoid accidental mutation.

  • var: a standard mutable variable that can be changed or reassigned.
  • val: an immutable variable that cannot be changed once initialized.
var a = 0       // type inferred as Int
a = 5           // a is mutable, so reassignment is ok

val b = 1	    // type inferred as Int as well
// b = 2	    // error because b is immutable

var c:Int = 10  // explicit type provided in this case

click to run
https://pl.kotl.in/WowggEtbB

# Operators

Kotlin supports a wide range of operators. The full set can be found on the Kotlin Language Guide.

# NULL Safety

NULL is a special value that indicates that there is no data present (often indicated by the null keyword in other languages). NULL values can be difficult to work with in other programming languages, because once you accept that a value can be NULL, you need to check all uses of that variable against the possibility of it being NULL.

NULL values are incredibly difficult to manage, because to address them properly means doing constant checks against NULL in return values, data, and so on. They add inherent instability to any type system.

Tony Hoare invented the idea of a NULL reference. In 2009, he apologized for this, famously calling it his "billion-dollar mistake."

In Kotlin, every type is non-nullable by default. This means that if you attempt to assign a NULL to a normal data type, the compiler is able to check against this and report it as a compile-time error. If you need to work with NULL data, you can declare a nullable variable using the ? annotation [ed. A nullable version of a type is actually a completely different type]. Once you do this, you need to use specific ? methods. You may also need to take steps to handle NULL data when appropriate.

Conventions

  • By default, a variable cannot be assigned a NULL value.
  • ? suffix on the type indicates that it’s NULL-able.
  • ?. accesses properties/methods if the object is not NULL ("safe call operator")
  • ?: elvis operator is a ternary operator for NULL data
  • !! override operator (calls a method without checking for NULL, bad idea)
fun main() {
	// name is nullable
	var name:String? = null

	// only returns value if name is not null
	var length = name?.length
	println(length) // null

	// elvis operator provides an `else` value
	length = name?.length ?: 0
	println(length) // 0
}

click to run
https://pl.kotl.in/2dLOgp7tA

# Generics

Generics are extensions to the type system that allows us to parameterize classes or functions across different types. Generics expand the reusability of your class definitions because they allow your definitions to work with many types.

We've already seen generics when dealing with collections:

val list: List<Int> = listOf(5, 10, 15, 20)

In this example, <Int> is specifying the type that is being stored in the list. Kotlin infers types where it can, so we typically write this as:

val list = listOf(5, 10, 15, 20)

We can use a generic type parameter in the place of a specific type in many places, which allows us to write code towards a generic type instead of a specific type. This prevents us from writing methods that might only differ by parameter or return type.

A generic type is a class that accepts an input of any type in its constructor. For instance, we can create a Table class that can hold differing values.

You define the class and make it generic by specifying a generic type to use in that class, written in angle brackets < >. The convention is to use T as a placeholder for the actual type that will be used.

fun main() {
	class Table<T>(t: T) {
	    var value = t
	}

	val table1: Table<Int> = Table<Int>(5)
	val table2 = Table<Float>(3.14f)
}

click to run
https://pl.kotl.in/Z4kN7gnxN

A more complete example:

import java.util.*

class Timeline<T>() {
    val events : MutableMap<Date, T> = mutableMapOf()

    fun add(element: T) {
        events.put(Date(), element)
    }

    fun getLast(): T {
        return events.values.last()
    }
}

fun main() {
	val timeline = Timeline<Int>()
	timeline.add(5)
	timeline.add(10)
}

click to run
https://pl.kotl.in/A1GXePIqY

# Control Flow

Kotlin supports the style of control flow that you would expect in an imperative language, but it uses more modern forms of these constructs

# if then else

if... then has both a statement form (no return value) and an expression form (return value).

fun main() {
  val a=5
  val b=7

  // we don't return anything, so this is a statement
  println("a=$a, b=$b")
  if (a > b) { 
      println("a is larger")
  } else { 
      println("b is larger")
  }

  val number = 6

  // the value from each branch is considered a return value
  // this is an expression that returns a result
  println("number=$number")
  val result = 
    if (number > 0)
      "$number is positive"
    else if (number < 0)
      "$number is negative"
    else 
      "$number is zero"

  println(result)
}

// a=5, b=7
// b is larger
// number=6
// 6 is positive

click to run
https://pl.kotl.in/YB6iszXPY

This is why Kotlin does not have a ternary operator: if used as an expression serves the same purpose.

# for in

A for in loop steps through any collection that provides an iterator. This is equivalent to the for each loop in languages like C#.

fun main() {
  val items = listOf("apple", "banana", "kiwifruit") 
  for (item in items) { 
  	println(item)
  }

  for (index in items.indices) { 
  	println("item $index is ${items[index]}")
  }

  for (c in "Kotlin") {
    print("$c ")
  }
}

// apple
// banana
// kiwifruit
// item 0 is apple
// item 1 is banana
// item 2 is kiwifruit
// K o t l i n 

click to run
https://pl.kotl.in/FtERd95GP

Kotlin doesn't support a C/Java style for loop. Instead, we use a range collection .. that generates a sequence of values.

fun main() {
  // invalid in Kotlin	
  // for (int i=0; i < 10; ++i)

  // range provides the same funtionality
  for (i in 1..3) { 
    print(i) 
  }
  println() // space out our answers

  // descending through a range, with an optional step
  for (i in 6 downTo 0 step 2) { 
    print("$i ") 
  } 
  println()

  // we can step through character ranges too
  for (c in 'A'..'E') {
    print("$c ")
  }
  println()

  // Check if a number is within range: 
  val x = 10
  val y = 9
  if (x in 1..y+1) { 
    println("fits in range")
  }
}

// 123
// 6 4 2 0 
// A B C D E 
// fits in range

click to run
https://pl.kotl.in/ksEaJQvoq

# while

while and do... while exist and use familiar syntax.

fun main() {
  var i = 1
  while ( i <= 10) {
    print("$i ")
    i++
  }
}

// 1 2 3 4 5 6 7 8 9 10 

click to run
https://pl.kotl.in/5HpDRHnjg

# when

when replaces the switch operator of C-like languages:

fun main() {
  val x = 2
  when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> print("x is neither 1 nor 2") 
  }
}

// x == 2

click to run
https://pl.kotl.in/B4NPMI6M2

fun main() {
    val x = 13
    val validNumbers = listOf(11,13,17,19)

    when (x) {
    	0, 1 -> print("x == 0 or x == 1")
    	in 2..10 -> print("x is in the range")
    	in validNumbers -> print("x is valid")
    	!in 10..20 -> print("x is outside the range")
    	else -> print("none of the above")
  }
}

// x is valid

click to run
https://pl.kotl.in/JjhqwVH7F

We can also return a value from when. Here's a modified version of this example:

fun main() {
    val x = 13
    val validNumbers = listOf(11,13,17,19)

    val response = when (x) {
        0, 1 -> "x == 0 or x == 1"
        in 2..10 -> "x is in the range"
        in validNumbers -> "x is valid"
        !in 10..20 -> "x is outside the range"
        else -> "none of the above"
    }
    println(response)
}

// x is valid

click to run
https://pl.kotl.in/0NyTPTPH-

When is flexible. To evaluate any expression, you can move the comparison expressions into when statement itself:

fun main() {
    val x = 13

    val response = when {
        x < 0 -> "negative"
        x >= 0 && x <= 9 -> "small"
        x >=10 -> "large"
        else -> "how do we get here?"
    }
    println(response)
}

// large

click to run
https://pl.kotl.in/ZjlrfAV7V

# return

Kotlin has three structural jump expressions:

  • return by default returns from the nearest enclosing function or anonymous function
  • break terminates the nearest enclosing loop
  • continue proceeds to the next step of the nearest enclosing loop

# Functions

Functions are preceded with the fun keyword. Function parameters require types, and are immutable. Return types should be supplied after the function name, but in some cases may also be inferred by the compiler.

# Named Functions

Named functions has a name assigned to them that can be used to invoke them directly (this is the expected form of a "function" in most cases, and the form that you're probably expecting).

// no parameters required
fun main() {
    println(sum1(1, 2))
    println(sum1(3,4))
}

// parameters which require type annotations
fun sum1(a: Int, b: Int): Int { 
    return a + b 
} 

// return types can be inferred based on the value you return
// it's better form to explicitly include the return type in the signature
fun sum2(a: Int, b: Int) {
    a + b // Kotlin knows that (Int + Int) -> Int
}

// 3
// 7

click to run
https://pl.kotl.in/ElMM9o5BG

# Single-Expression Functions

Simple functions in Kotlin can sometimes be reduced to a single line aka a single-expression function.

// previous example
fun sumOf(a: Int, b: Int):Int {
    return a + b 
}

// this works since we evaluate a single expression
fun minOf(a: Int, b: Int) = if (a < b) a else b

fun main() {
    println(sumOf(5,10))
    println(minOf(10,20))
}

// 15
// 10

click to run
https://pl.kotl.in/pglN5A1UA

# Default arguments

We can use default arguments for function parameters. When called, a parameter with a default value is optional; if the caller does not provide the value, then the default will be used.

// Second parameter has a default value, so it’s optional
fun mult(a:Int, b:Int = 5): Int { 
	return a * b 
} 

fun main() {
	println(mult(1)) // a=1, b=5 default
	println(mult(5,2)) // a=5, b=2
	// mult() would throw an error, since `a` must be provided
}

click to run
https://pl.kotl.in/OyTYnCgFH

# Named parameters

You can (optionally) provide the parameter names when you call a function. If you do this, you can even change the calling order!

fun repeat(str:String="*", count:Int=1):String {
    return str.repeat(count)
}

fun main() {
	println(repeat()) // *
	println(repeat(str="#")) // *
	println(repeat(count=3)) // ***
	println(repeat(str="#", count=5)) // #####
	println(repeat(count=5, str="#")) // #####
}

// *
// #
// ***
// #####
// #####

click to run
https://pl.kotl.in/a-b3gJGXD

# Variable-length arguments

Finally, we can have a variable length list of arguments:

fun sum(vararg numbers: Int): Int { 
    var sum = 0 
    for(number in numbers) { 
        sum += number
    }
  return sum 
} 

fun main() {
    println(sum(1)) // 1
    println(sum(1,2,3)) // 6 
    println(sum(1,2,3,4,5,6,7,8,9,10)) // 55
}

// 1
// 6
// 55

click to run
https://pl.kotl.in/gUy7F7r23

# Collections

A collection is a finite group of some variable numbers of items (possibly zero) of the same type. Objects in a collection are called elements.

Collections in Kotlin are contained in the kotlin.collections package, which is part of the Kotlin Standard Library.

These collection classes exist as generic containers for a group of elements of the same type e.g. List<Int> would be an ordered list of integers. Collections have a finite size, and are eagerly evaluated.

Kotlin offers functional processing operations (e.g. filter, map and so on) on each of these collections.

fun main() {
  val list = (1..10).toList() // generate list of 1..10
  println( list.take(5).map{it * it} ) // square the first 5 elements
}

// [1, 4, 9, 16, 25]

click to run
https://pl.kotl.in/hO7F0XmC_

Under-the-hood, Kotlin uses Java collection classes, but provides mutable and immutable interfaces to these classes. Kotlin best-practice is to use immutable for read-only collections whenever possible (since mutating collections is often very costly in performance).

Collection Class Description
Pair A tuple of two values.
Triple A tuple of three values.
List An ordered collection of objects.
Set An unordered collection of objects.
Map An associative dictionary of keys and values.
Array An indexed, fixed-size collection of objects.

A tuple is a data structure representing a sequence of n elements.

# Pair

A Pair is a tuple of two values. Use var or val to indicate mutability. Theto keyword can be used to indicate a Pair.

fun main() {
  // mutable 
  var nova_scotia = "Halifax Airport" to "YHZ" 
  var newfoundland = Pair("Gander Airport", "YQX") 
  var ontario = Pair("Toronto Pearson", "YYZ") 
  ontario = Pair("Billy Bishop", "YTZ") // reassignment is ok

  // accessing elements
  val canadian_exchange = Pair("CDN", 1.38)  
  println(canadian_exchange.first) // CDN
  println(canadian_exchange.second) // 1.38

  // destructuring
  val (first, second) = Pair("Calvin", "Hobbes") // split a Pair
  println(first) // Calvin
  println(second) // Hobbes
}

// CDN
// 1.38
// Calvin
// Hobbes

click to run
https://pl.kotl.in/CqaNH75rD

Pairs are extremely useful when working with data that is logically grouped into tuples, but where you don't need the overhead of a custom class., e.g. Pair for 2D points.

# List

A List is an ordered collection of objects.

fun main() {
  // define an immutable list
  var fruits = listOf( "advocado", "banana") 
  println(fruits.get(0))
  // advocado

  // add elements
  var mfruits = mutableListOf( "advocado", "banana") 
  mfruits.add("cantaloupe")
  mfruits.forEach { println(it) }

  // sorted/sortedBy returns ordered collection 
  val list = listOf(2,3,1,4).sorted() // [1, 2, 3, 4] 
  list.sortedBy { it % 2 } // [2, 4, 1, 3] 

  // groupBy groups elements on collection by key 
  list.groupBy { it % 2 } // Map: {1=[1, 3], 0=[2, 4]} 

  // distinct/distinctBy returns unique elements 
  listOf(1,1,2,2).distinct() // [1, 2] 
}

// advocado
// advocado
// banana
// cantaloupe

click to run
https://pl.kotl.in/KOAcbfoPp

# Set

A Set is a generic unordered collection of unique elements (i.e. it does not support duplicates, unlike a List which does). Sets are commonly constructed with helper functions:

fun main() {
	val numbersSet = setOf("one", "two", "three", "four")
    println(numbersSet)

	val emptySet = mutableSetOf<String>()
    println(emptySet)
}

// [one, two, three, four]
// []

click to run
https://pl.kotl.in/95_od6HSG

# Map

A Map is an associative dictionary containing Pairs of keys and values.

fun main() {
  // immutable reference, immutable map
  val imap = mapOf(Pair(1, "a"), Pair(2, "b"), Pair(3, "c")) 
  println(imap)
  // {1=a, 2=b, 3=c}

  // immutable reference, mutable map (so contents can change)
  val mmap = mutableMapOf(5 to "d", 6 to "e") 
  mmap.put(7,"f")
  println(mmap) 
  // {5=d, 6=e, 7=f}

  // lookup a value 
  println(mmap.get(5)) 
  // d

  // iterate over key and value
  for ((k, v) in imap) { 
    print("$k=$v ") 
  } 
  // 1=a 2=b 3=c

  // alternate syntax
  imap.forEach { k, v -> print("$k=$v ") }
  // 1=a 2=b 3=c

  // `it` represents an implicit iterator
  imap.forEach {
    print("${it.key}=${it.value} ")
  }
  // 1=a 2=b 3=c
}

// {1=a, 2=b, 3=c}
// {5=d, 6=e, 7=f}
// d
// 1=a 2=b 3=c 1=a 2=b 3=c 1=a 2=b 3=c 

click to run
https://pl.kotl.in/3sArvFTMd

# Functions

Collection classes (e.g. List, Set, Map, Array) have built-in functions for working with the data that they contain. These functions frequently accept other functions as parameters.

# Filter

filter produces a new list of those elements that return true from a predicate function.

fun main() {
	val list = (1..100).toList()
	val filtered = list.filter { it % 5 == 0 }
    println(filtered)
	// 5 10 15 20 ... 100

	val below50 = filtered.filter { it in 0..49 }
    println(below50)
	// [5, 10, 15, 20]
}

// [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]
// [5, 10, 15, 20, 25, 30, 35, 40, 45]

click to run
https://pl.kotl.in/8e8hX552r

# Map

map produces a new list that is the results of applying a function to every element that it contains.

fun main() {
	val list = (1..100).toList()
	val doubled = list.map { it * 2 }
    println(doubled)
}

// 2 4 6 8 ... 200

click to run
https://pl.kotl.in/cER3LxQGC

# Reduce

reduce accumulates values starting with the first element and applying an operation to each element from left to right.

fun main() {
	val strings = listOf("a", "b", "c", "d")
	println(strings.reduce { acc, string -> acc + string }) // abcd
}

// abcd

click to run
https://pl.kotl.in/6pIwMfznv

# Zip

zip combines two collections, associating their respective pairwise elements.

fun main() {
	val foods = listOf("apple", "kiwi", "broccoli", "carrots")
	val fruit = listOf(true, true, false, false)
    println(fruit)

	val results = foods.zip(fruit)
	println(results)
}

// [true, true, false, false]
// [(apple, true), (kiwi, true), (broccoli, false), (carrots, false)]

click to run
https://pl.kotl.in/H9yNIFh6V

A more realistic scenario might be where you want to generate a pair based on the results of the list elements:

fun main() {
	val list = listOf("123", "", "456", "def")
	val exists = list.zip(list.map { !it.isBlank() })
    println(exists)

	val numeric = list.zip(list.map { !it.isEmpty() && it[0] in ('0'..'9') })
    println(numeric)
}

// [(123, true), (, false), (456, true), (def, true)]
// [(123, true), (, false), (456, true), (def, false)]

click to run
https://pl.kotl.in/vcfyEcFly

# ForEach

forEach calls a function for every element in the collection.

fun main() {
	val fruits = listOf("advocado", "banana", "cantaloupe" )
	fruits.forEach { print("$it ") }
}

// advocado banana cantaloupe

click to run
https://pl.kotl.in/Vu8DnGQ6l

We also have helper functions to extract specific elements from a list.

# Take

take returns a collection containing just the first n elements. drop returns a new collection with the first n elements removed.

fun main() {
	val list = (1..50)
	val first10 = list.take(10) 
	println(first10)

	val last40 = list.drop(10) 
	println(last40)
}

// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// [11, 12, 13, 14, 15, 16, 17, 18, 19, ...]

click to run
https://pl.kotl.in/cNE0aYTIv

# First, Last, Slice

first and last return those respective elements. slice allows us to extract a range of elements into a new collection.

fun main() {
	val list = (1..50)
	val even = list.filter { it % 2 == 0 }

	println(even.first()) // 2
	println(even.last()) // 50
	println(even.slice(1..3)) // 4 6 8
}

// 2
// 50
// [4, 6, 8]

click to run
https://pl.kotl.in/5fUnFhf_0

# Object-Oriented Kotlin

Object-oriented programming is a refinement of the structured programming model that was discovered in 1966 by Ole-Johan Dahl and Kristen Nygaard.

It is characterized by the use of classes as a template for common behaviour (methods) and data (state) required to model that behaviour.

  • Abstraction: Model entities in the system to match real world objects. Each object exposes a stable high-level interface that can be used to access its data and behaviours. “Changing the implementation should not break anything.”
  • Encapsulation: Keep state and implementation private.
  • Inheritance: Specialization of classes. Create a specialized (child) class by deriving from another (parent) class, and reusing the parent’s fields and methods.
  • Polymorphism: Base and derived classes share an interface, but have specialized implementations.

Object-oriented programming has benefits over iterative programming:

  • It supports abstraction and allows us to express computation in a way that models our problem domain (e.g. customer classes, file classes).
  • It handles complex state more effectively, by delegating to classes.
  • It’s also a method of organizing your code. This is critical as programs grow.
  • There is some suggestion that it makes code reuse easier (debatable).
  • It became the dominant programming model in the 80s, and our most used languages are OO languages. e.g. C++, Java, Swift, Kotlin.

# Classes

Kotlin is a class-based object-oriented language, with some advanced features that it shares with some other recent languages. The class keyword is used to define a Class. You create an instance of the class using the class name (no new keyword required!)

 // define class
 class Person

 // create two instances and assign to p, q
 // note that we have an implicit no-arg constructor
 val p = Person()
 val q = Person()

Classes include properties (values) and methods.

# Properties

A property is a variable declared in a class, but outside methods or functions. They are analogous to class members, or fields in other languages.

class Person() {
   var firstName = "Vanilla"
   var lastName = "Ice"
}

fun main() {
	val p = Person()

    // we can access properties directly
    // this calls an implicit get() method; default returns the value
    println("${p.firstName} ${p.lastName} ${p.lastName} Baby")
}

click to run
https://pl.kotl.in/lZFfs--lV

All Properties have implicit backing fields that store their data. We can override the get() and set methods to determine how our properties interact with the backing fields.

For example, for a City class, we can decide that we want the city name always reported in uppercase, and we want the population always stored as thousands.

 // the backing field is just referred to as `field`
 // in the set() method, we use `value` as the argument
 class City() {
   var name = ""
     get() = field.uppercase()
     set(value) {
       field = value
     }
   var population = 0
     set(value) {
       field = value/1_000
     }
 }

 fun main() {
     // create our city, using properties to access values
     val city = City()
     city.name = "Halifax"
     city.population = 431_000
     println("${city.name} has a population of ${city.population} thousand people")
 }

click to run
https://pl.kotl.in/i_GZcU0_4

Behind-the-scenes, Kotlin is actually creating getter and setter methods, using the convention of getField and setField. In other words, you always have corresponding methods that are created for you. If you directly access the field name, these methods are actually getting called in the background.

Venkat Subramaniam has an excellent example of this (Subramaniam 2019). Write the class Car in a separate file named Car.kt:

 class Car(val yearOfMake: Int, var color: String)

Then compile the code and take a look at the bytecode using the javap tool, by running these commands:

 $ kotlinc-jvm Car.kt
 $ javap -p Car.class

This will display the bytecode generated by the Kotlin Compiler for the Car class:

 public final class Car {
   private final int yearOfMake;
   private java.lang.String color;
   public final int getYearOfMake();
   public final java.lang.String getColor();
   public final void setColor(java.lang.String);
   public Car(int, java.lang.String);
 }

That concise single line of Kotlin code for the Car class resulted in the creation of two fields—the backing fields for properties, a constructor, two getters, and a setter.

# Constructors

Like other OO languages, Kotlin supports explicit constructors that are called when objects are created.

# Primary Constructors

A primary constructor is the main constructor that your class will support (representing how you want it to be instantiated most of the time). You define it by expanding the class definition:

 // class definition includes the primary constructor
 class Person constructor() { }

 // we can collapse this to define an explicit no-arg constructor
 class Person() {}

In the example above, the primary constructor is called when this class is instantiated.

Optionally, you can include parameters in the primary constructor, and use these to initialize parameters in the constructor body.

 // constructor with arguments
 // this uses the parameters to initialize properties (i.e. variables)
 class Person (first:String, last:String) {
   val firstName = first.take(1).uppercase() + first.drop(1).lowercase()
   val lastName = last.take(1).uppercase() + last.drop(1).lowercase()

   // adding a statement like this will prevent the code from compiling
   // println("${firstname} ${lastname}")  // will not compile
 }

 fun main() {
	 // this does not work! we do not have a no-arg constructor
	 // val person = Person() // error since no matching constructor

	 // this works and demonstrates the properties
	 val person = Person("JEFF", "AVERY")
	 println("${person.firstName} ${person.lastName}") // Jeff Avery
 }

click to run
https://pl.kotl.in/xgtEWjLVi

Constructors are designed to be minimal:

  • Parameters can only be used to initialize properties. They go out of scope immediately after the constructor executes.
  • You cannot invoke any other code in your constructor (there are other ways to handle that, which we will discuss below).
# Secondary Constructors

What if you need more than a single constructor?

You can define secondary constructors in your class. Secondary constructors must delegate to the primary constructor. Let's rewrite this class to have a primary no-arg constructor, and a second constructor with parameters.

// primary constructor
class Person() {
	// initialize properties
   	var firstName = "PAULA"
   	var lastName = "ABDUL"

   	// secondary constructor
   	// delegates to the no-arg constructor, which will be executed first
   	constructor(first: String, last: String) : this() {
   		// assign to the properties defined in the primary constructor
   		firstName = first.take(1).uppercase() + first.drop(1).lowercase()
   		lastName = last.take(1).uppercase() + last.drop(1).lowercase()
   	}
}

fun main() {
	val person1 = Person() // primary constructor using default property values
	println("${person1.firstName} ${person1.lastName}")

	val person2 = Person("JEFF", "AVERY") // secondary constructor
	println("${person2.firstName} ${person2.lastName}")
}

click to run
https://pl.kotl.in/h588rhQgQ

# Init Blocks

How do we execute code in the constructor? We often want to do more than initialize properties.

Kotlin has a special method called init() that is used to manage initialization code. You can have one or more of these init blocks in your code, which will be called in order after the primary constructor (they're actually considered part of the primary constructor). The order of initialization is (1) primary constructor, (2) init blocks in listed order, and then finally (3) secondary constructor.

class InitOrderDemo(name: String) { 
   val first = "$name".also(::println) 

   init { 
     println("First init: ${first.length}")
   }

   val second = "$name".also(::println) 
   init { 
     println("Second init: ${second.length}")
     }
 } 

fun main() {
    InitOrderDemo("Jeff")
}

Why does Kotlin split the constructor up like this? It's a way to enforce that initialization MUST happen first, which results in cleaner and safer code.

# Class Methods

Similarly to other programming languages, functions defined inside a class are called methods.


class Person(var firstName: String, var lastName: String) {
   fun greet() {
     println("Hello! My name is $firstName")
   }
 }

 fun main() {
 	val person = Person ("Jeff", "Avery")
 	println("${person.firstName} ${person.lastName}")
 }

# Inheritance

To derive a class from a supertype, we use the colon : operator. We also need to delegate to the base class constructor using ().

By default, classes and methods are closed to inheritance. If you want to extend a class or method, you need to explicitly mark it as open for inheritance.

 class Base
 class Derived : Base() // error!

 open class Base 
 class Derived : Base() // ok

Kotlin supports single-inheritance.

open class Person(val name: String) { 
   open fun hello() = "Hello, I am $name" 
 }

 class PolishPerson(name: String) : Person(name) { 
   override fun hello() = "Dzien dobry, jestem $name" 
 }

 fun main() {
     val p1 = Person("Jerry")
     val p2 = PolishPerson("Beth")
     println(p1.hello())
     println(p2.hello())
 }

If the derived class has a primary constructor, the base class can (and must) be initialized, using the parameters of the primary constructor. If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword, or to delegate to another constructor which does that.

 class MyView : View {
     constructor(ctx: Context) : super(ctx)
     constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
 }

All classes in Kotlin have a common superclass Any, that is the default superclass for a class with no supertypes declared:

 class Example // Implicitly inherits from Any

Any has three methods: equals(), hashCode() and toString(). Thus, they are defined for all Kotlin classes.

# Abstract Classes

Classes can be declared abstract, which means that they cannot be instantiated, only used as a supertype. The abstract class can contain a mix of implemented methods (which will be inherited by subclasses) and abstract methods, which do not have an implementation.

// useful way to represent a 2D point
 data class Point(val x:Int, val y:Int)

 abstract class Shape() {
   // we can have a single representation of position
   var x = 0
   var y = 0

   fun position(): Point {
     return Point(x, y)
   }
   // subtypes will have their own calculations for area
   abstract fun area():Int
 }

 class Rectangle (var width: Int, var height: Int): Shape() {
   constructor(x: Int, y: Int, width: Int, height: Int): this(width, height) {
     this.x = x
     this.y = y
   }
   // must be overridden since our base is abstract
   override fun area():Int {
     return width * height
   }
 }

 fun main() {
	// this won't compile, since Shape() is abstract
	// val shape = Shape()

	// this of course is fine
	val rect = Rectangle(10, 20, 50, 10)
	println("Rectangle at (${rect.position().x},${rect.position().y}) with area ${rect.area()}")
	// => Rectangle at (10,20) with area 500
 }
# Interfaces

Interfaces in Kotlin are similar to abstract classes, in that they can contain a mix of abstract and implemented methods. What makes them different from abstract classes is that they cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.

# Data Classes

A data class is a special type of class, which primarily exists to hold data, and does not have custom methods. Classes like this are more common than you expect – we often create trivial classes to just hold data, and Kotlin makes them simple to create.

Why would you use a data class over a regular class? It generates a lot of useful methods for you:

  • hashCode()
  • equals() // compares fields
  • toString()
  • copy() // using fields
  • destructuring

Here's an example of how useful this can be:

data class Person(val name: String, var age: Int) 

fun main() {
	val mike = Person("Mike", 23) 

	// toString() displays all properties 
	println(mike.toString())

	// structural equality (==) compares properties
	println(mike == Person("Mike", 23)) // True 
	println(mike == Person("Mike", 21)) // False 

	// referential equality (===) compares object references
	println(mike === Person("Mike", 23)) // False 

	// hashCode based on primary constructor properties
	println(mike.hashCode() == Person("Mike", 23).hashCode()) // True
	println(mike.hashCode() == Person("Mike", 21).hashCode()) // False 

	// destructuring based on properties
	val (name, age) = mike 
	println("$name $age") // Mike 23 

	// copy that returns a copy of the object 
	// with concrete properties changed
	val jake = mike.copy(name = "Jake") // copy 
}

# Enum Classes

Enums in Kotlin are classes, so enum classes support type safety.

We can use them in expected ways. Enum num constants are separated with commas. We can also do interesting things with our enums, e.g. use them in when clauses (Example from Sommerhoff 2020).

 enum class Suits {
     HEARTS, SPADES, DIAMONDS, CLUBS
 }

fun main() {
 	val color = when(Suits.SPADES) {
   		Suits.HEARTS, Suits.DIAMONDS -> "red"
   		Suits.SPADES, Suits.CLUBS -> "black"
 	}
 	println(color)
}

Each enum constant is an object, and can be instantiated.

enum class Direction(val degrees: Double) {
	NORTH(0.0), SOUTH(180.0), WEST(270.0), EAST(90.0)
}

fun main() {
	val direction = Direction.EAST
	print(direction.degrees)
}

# Visibility Modifiers

Classes, objects, interfaces, constructors, functions, properties and their setters can have visibility modifiers. Getters always have the same visibility as the property. Kotlin defaults to public access if no visibility modifier is provided.

The possible visibility modifiers are:

  • public: visible to any other code.
  • private: visible inside this class only (including all its members).
  • protected: visible to any derived class, otherwise private.
  • internal: visible within the same module, where a module is a set of Kotlin files compiled together.

# Operator Overloading

Kotlin allows you to provide custom implementations for the predefined set of operators. These operators have predefined symbolic representation (like + or *) and precedence if you combine them.

Basically, you use the operator keyword to define a function, and provide a member function or an extension function with a specific name for the corresponding type. This type becomes the left-hand side type for binary operations and the argument type for the unary ones.

Here's an example that extends a class named ClassName by overloading the + operator.

data class Point(val x: Double, val y: Double)

// -point
operator fun Point.unaryMinus() = Point(-x, -y)

// p1+p2
operator fun Point.plus(other: Point) = Point(this.x + other.x, this.y + other.y)

// p1*5
operator fun Point.times(scalar: Int) = Point(this.x * scalar, this.y * scalar)
operator fun Point.times(scalar: Double) = Point(this.x * scalar, this.y * scalar)

fun main() {
    val p1 = Point(5.0, 10.0)
	val p2 = Point(10.0, 12.0)

    println("p1=${p1}")
    println("p2=${p2}\n")
    println("-p1=${-p1}")
    println("p1+p2=${p1+p2}")
    print("p2*5=${p2*5}")    
}

We can override any operators by using the keyword that corresponds to the symbol we want to override.

Note that this is the reference object on which we are calling the appropriate method. Parameters are available as usual.

Description Expression Translated to
Unary prefix +a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
Increments, decrements a++ a.inc()
a-- a.dec()
Arithmetic a+b a.plus(b)
a-b a.minus(b)
a*b a.times(b)
a/b a.div(b)
a%b a.rem(b)
a..b a.rangeTo(b)
In a in b b.contains(a)
Augmented assignment a+=b a.plusAssign(b)
a-=b a.minusAssign(b)
a*=b a.timesAssign(b)
a/=b a.divAssign(b)
a%b a.remAssign(b)
Equality a==b a?.equals(b) ?: (b === null)
a!=b !(a?.equals(b) ?: (b === null))
Comparison a>b a.compareTo(b) > 0
a<b a.compareTo(b) < 0
a>=b a.compareTo(b) >= 0
a<=b a.compareTo(b) <= 0

# Infix Functions

Functions marked with the infix keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must meet the following requirements:

For example, we can add a "shift left" function to the built-in Int class:


infix fun Int.shl(x: Int): Int { 
	return (this shl x)
}

fun main() {
	// calling the function using the infix notation
	// shl 1 multiples an int by 2
 	println(212 shl 1)

 	// is the same as
 	println(212.shl(1))
}

# Extension Functions

Kotlin supports extension functions: the ability to add functions to existing classes, even when you don't have access to the original class's source code, or cannot modify the class for some reason. This is also a great alternative to inheritance when you cannot extend a class.

For a simple example, imagine that you want to determine if an integer is even. The "traditional" way to handle this is to write a function:

fun isEven(n: Int): Boolean = n % 2 == 0

fun main() {
	println(isEven(4))
	println(isEven(5))
}

In Kotlin, the Int class already has a lot of built-in functionality. It would be a lot more consistent to add this as an extension function to that class.

fun Int.isEven() = this % 2 == 0

fun main() {
    println(4.isEven())
    println(5.isEven())
}

You can use extensions with your own types and also types you do not control, like List, String, and other types from the Kotlin standard library.

Extension functions are defined in the same way as other functions, with one major difference: When you specify an extension function, you also specify the type the extension adds functionality to, known as the receiver type. In our earlier example, Int.isEven(), we need to include the class that the function extends, or Int.

Note that in the extension body, this refers to the instance of the type (or the receiver for this method invocation).

fun String.addEnthusiasm(enthusiasmLevel: Int = 1) = this + "!".repeat(enthusiasmLevel)

fun main() {
    val s1 = "I'm so excited"
    val s2 = s1.addEnthusiasm(5)
    println(s2)
}

# Defining an extension on a superclass

Extensions do not rely on inheritance, but they can be combined with inheritance to expand their scope. If you extend a superclass, all of its subclasses will inherit the extension method that you defined.

Define an extension on the Any class called print. Because it is defined on Any, it will be directly callable on all types.

// Any is the top-level class from which all classes derive i.e. the ultimate superclass.

fun Any.print() {
  println(this)
}

fun main() {
	"string".print()
	42.print()
}

# Extension Properties

In addition to adding functionality to a type by specifying extension functions, you can also define extension properties.

For example, here is an extension property that counts a string’s vowels:

val String.numVowels
    get() = count { it.lowercase() in "aeiou" }

fun main() {
    println("abcd".numVowels)
}

# Destructuring

Sometimes it is convenient to destructure an object into a number of variables. This syntax is called a destructuring declaration. A destructuring declaration creates multiple variables at once. In the example below, you declared two new variables: name and age, and can use them independently:

data class Person(val name: String, val age: Int)

fun main() {
  val p = Person("Janine", 38)
	val (name, age) = p  // destructuring
	println(name)
	println(age)
}

A destructuring declaration is compiled down to the following code:

 val name = person.component1()
 val age = person.component2()

component1(), component2() are aliases to the named properties in this class, in the order they were declared (and, of course, there can be component3() and component4() and so on). You would never normally refer to them using these aliases.

Here's an example from the Kotlin documentation on how to use this to return multiple values from a function:

 // data class with properties `result` and `status`
 data class Result(val result: Int, val status: Status)

 fun function(...): Result {
     // computations
     return Result(result, status)
 }

 // Destructure into result and status
 val (result, status) = function(...)

 // We can also choose to not assign fields
 // e.g. we could just return `result` and discard `status`
 val (result, _) = function(...)

# Companion Objects

OO languages typically have some idea of static members: methods that are associated with a class instead of an instance of a class. Static methods can be useful when attempting to implement the singleton pattern, for instance.

Kotlin doesn't support static members directly. To get something comparable in Kotlin, you need to declare a companion object as an inner class of an existing class. Any methods that are created as part of the companion object are considered to be static methods in the enclosing class.

The examples below are taken from: https://livevideo.manning.com/module/59_5_16/kotlin-for-android-and-java-developers

class House(val numberOfRooms: Int, val price: Double) {
   companion object {
     val HOUSES_FOR_SALE = 10
     fun getNormalHouse() = House(6, 599_000.00)
     fun getLuxuryHouse() = House(42, 7_000_000.00)
   }
 }

 fun main() {
   val normalHouse = House.getNormalHouse()  // works 
   println(normalHouse.price)
   println(House.HOUSES_FOR_SALE)
 }

We can also use object types to implement singletons. All we need to do is use the object keyword.

 class Country(val name:String) {
     var area = 0.0
 }

 // there can be only one
 object CountryFactory {
     fun createCountry() = Country("Canada")
 }

 fun main() {
     val obj = CountryFactory.createCountry()
     println(obj.name)
 }

# Functional Kotlin

Functional programming is a programming style where programs are constructed by compositing functions together. Functional programming treats functions as first-class citizens: they can be assigned to a variable, passed as parameters, or returned from a function.

Functional programming also specifically avoids mutation: functions transform inputs to produce outputs, with no internal state. Functional programming can be described as declarative (describe what you want) instead of imperative (describe how to accomplish a result).

Functional programming constrains assignment, and therefore constrains side effects.
-- Bob Martin, 2003.

Kotlin is considered a hybrid language: it provides mechanisms for you to write in a functional style, but it also doesn't prevent you from doing non-functional things. As a developer, it's up to you to determine the most appropriate approach to a given problem.

Here are some common properties that we talk about when referring to "functional programming":

First-class functions means that functions are treated as first-class citizens. We can pass them as to another function as a parameter, return functions from other functions, and even assign functions to variables. This allows us to treat functions much as we would treat any other variable.

Pure functions are functions that have no side effects. More formally, the return values of a pure function are identical for identical arguments (i.e. they don't depend on any external state). Also, by having no side effects, they do not cause any changes to the system, outside their return value. Functional programming attempts to reduce program state, unlike other programming paradigms (imperative or object-oriented) which are based on careful control of program state.

Immutable data means that we do not modify data in-place. We prefer immutable data that cannot be accidentally changed, especially as a side effect of a function. Instead, if we need to mutate data, we pass it to a function that will return a new data structure containing the modified data, leaving the original data intact. This avoids unintended state changes.

Lazy evaluation is the notion that we only evaluate an expression when we need to operate on it (and we only evaluate what we need to evaluate at the moment!) This allows us to express and manipulate some expressions that would be extremely difficult to actually represent in other paradigms.

# Function Types

Functions in Kotlin are "first-class citizens" of the language. This means that we can define functions, assign them to variables, pass functions as arguments to other functions, or return functions! Functions are types in Kotlin, and we can use them anywhere we would expect to use a regular type.

Dave Leeds on Kotlin presents the following excellent example: Bert's Barber shop is creating a program to calculate the cost of a haircut, and they end up with 2 almost-identical functions.

fun main() {
    val taxMultiplier = 1.10

    fun calculateTotalWithFiveDollarDiscount(initialPrice: Double): Double {
      val	priceAfterDiscount = initialPrice - 5.0
      val total = priceAfterDiscount * taxMultiplier 
      return total
    }

    fun calculateTotalWithTenPercentDiscount(initialPrice: Double): Double {
      val priceAfterDiscount = initialPrice * 0.9
      val total = priceAfterDiscount * taxMultiplier
      return total
    }
}

These functions are identical except for the line that calculates priceAfterDiscount. If we could somehow pass in that line of code as an argument, then we could replace both with a single function that looks like this, where applyDiscount() represents the code that we would dynamically replace:

// applyDiscount = initialPrice * 0.9, or
// applyDiscount = initialPrice - 5.0
fun calculateTotal(initialPrice: Double, applyDiscount: ???): Double {
    val priceAfterDiscount = applyDiscount(initialPrice)
    val total = priceAfterDiscount * taxMultiplier
    return total
}

This is a perfect scenario for passing in a function!

# Assign a function to a variable

fun discountFiveDollars(price: Double): Double = price - 5.0
val applyDiscount = ::discountFiveDollars

In this example, applyDiscount is now a reference to the discountFiveDollars function (note the :: notation when we have a function on the RHS of an assignment). We can even call it.

val discountedPrice = applyDiscount(20.0) // Result is 15.0

So what is the type of our function? The type of function is the function signature, but with a different syntax that you might be accustomed to seeing.

// this is the original function signature
fun discountFiveDollars(price: Double): Double = price - 5.0
val applyDiscount = ::discountFiveDollars

// applyDiscount accepts a Double as an argument and returns a Double
// we use this format when specifying the type
val applyDiscount: (Double) -> Double

For functions with multiple parameters, separate them with a comma.

We can use this notation when explicitly specifying type.

fun discountFiveDollars(price: Double): Double = price - 5.0

// specifying type is not necessary since type inference works too
// we'll just do it here to demonstrate how it would appear
val applyDiscount : (Double) -> Double = ::discountFiveDollars

# Pass a function to a function

We can use this information to modify the earlier example, and have Bert's calculation function passed into the second function.

fun discountFiveDollars(price: Double): Double = price - 5.0
fun discountTenPercent(price: Double): Double = price * 0.9
fun noDiscount(price: Double): Double = price

fun calculateTotal(initialPrice: Double, applyDiscount: (Double) -> Double): Double {
    val priceAfterDiscount = applyDiscount(initialPrice)
    val total = priceAfterDiscount * taxMultiplier
    return total
}

val withFiveDollarsOff = calculateTotal(20.0, ::discountFiveDollars) // $16.35
val withTenPercentOff  = calculateTotal(20.0, ::discountTenPercent)  // $19.62
val fullPrice          = calculateTotal(20.0, ::noDiscount)          // $21.80

# Returning Functions from Functions

Instead of typing in the name of the function each time he calls calculateTotal(), Bert would like to just enter the coupon code from the bottom of the coupon that he receives from the customer. To do this, he just needs a function that accepts the coupon code and returns the right discount function.

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> ::discountFiveDollars
    "TAKE_10"    -> ::discountTenPercent
    else         -> ::noDiscount
}

I've taken liberties with Dave Leed's example, but my notes can't do it justice. I'd highly recommend a read through his site - he's building an outstanding Kotlin book chapter-by-chapter with cartoons and illustrations.

# Lambdas

We can use this same notation to express the idea of a function literal, or a function as a value.

val applyDiscount: (Double) -> Double = { price: Double -> price - 5.0 }

The code on the RHS of this expression is a function literal, which captures the body of this function. We also call this a lambda. A lambda is just a function, but written in this form:

  • the function is enclosed in curly braces
  • the parameters are listed, followed by an arrow
  • the body comes after the arrow

What makes a lambda different from a traditional function is that it doesn't have a name. In the expression above, we assigned the lambda to a variable, which we could them use to reference it, but the function itself isn't named.

Note that due to type inference, we could rewrite this example without the type specified on the LHS. This is the same thing!

val applyDiscount = { price: Double -> price - 5.0 }

# The implicit 'it' parameter

In cases where there’s only a single parameter for a lambda, you can omit the parameter name and the arrow. When you do this, Kotlin will automatically make the name of the parameter it.

val applyDiscount: (Double) -> Double = { it - 5.0 }

# Lambdas and Higher-Order Functions

# Passing Lambdas as Arguments

Higher-order functions have a function as an input or output. We can rewrite our earlier example to use lambdas instead of function references:

// fun discountFiveDollars(price: Double): Double = price - 5.0
// fun discountTenPercent(price: Double): Double = price * 0.9
// fun noDiscount(price: Double): Double = price

fun calculateTotal(initialPrice: Double, applyDiscount: (Double) -> Double): Double {
    val priceAfterDiscount = applyDiscount(initialPrice)
    val total = priceAfterDiscount * taxMultiplier
    return total
}

val withFiveDollarsOff = calculateTotal(20.0, { price - 5.0 }) // $16.35
val withTenPercentOff  = calculateTotal(20.0, { price * 0.9 }) // $19.62
val fullPrice          = calculateTotal(20.0, { price })       // $21.80

In cases where function’s last parameter is a function type, you can move the lambda argument outside of the parentheses to the right, like this:

val withFiveDollarsOff = calculateTotal(20.0) { price -> price - 5.0 }
val withTenPercentOff  = calculateTotal(20.0) { price -> price * 0.9 }
val fullPrice          = calculateTotal(20.0) { price -> price }

This is meant to be read as two arguments: one inside the brackets, and the lambda as the second parameter.

# Returning Lambdas as Function Results

We can easily modify our earlier function to return a lambda as well.

fun discountForCouponCode(couponCode: String): (Double) -> Double = when (couponCode) {
    "FIVE_BUCKS" -> { price -> price - 5.0 }
    "TAKE_10"    -> { price -> price * 0.9 }
    else         -> { price -> price }
}

# Scope Functions

The Kotlin standard library contains several functions whose sole purpose is to execute a block of code on an object. When you call such a function on an object with a lambda expression, it forms a temporary scope, and applies the lambda to that object.

There are five of these scope functions: let, run, with, apply, and also, and each of them has a slightly different purpose.

Here's an example where we do not use one of these scope functions. There is a great deal of repetition, since we need a temporary variable, and then have to act on that object.

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

With a scope function, we can refer to the object without using a name. This is greatly simplified!

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

The scope functions have subtle differences in how they work, summarized from the Kotlin Standard Library documentation. Inside the lambda of a scope function, the context object is available by a short reference instead of its actual name. Each scope function uses one of two ways to access the context object: as a lambda receiver (this) or as a lambda argument (it).

Function Object reference Return value Is extension function
let it Lambda result Yes
run this Lambda result Yes
run - Lambda result No: called without the context object
with this Lambda result No: takes the context object as an argument.
apply this Context object Yes
also it Context object Yes

# let

The context object is available as an argument (it). The return value is the lambda result.

let can be used to invoke one or more functions on results of call chains. For example, the following code prints the results of two operations on a collection:

val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)    

With let, you can rewrite it:

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
    println(it)
    // and more function calls if needed
} 

# with

A non-extension function: the context object is passed as an argument, but inside the lambda, it's available as a receiver (this). The return value is the lambda result. We recommend with for calling functions on the context object without providing the lambda result. In the code, with can be read as “with this object, do the following.”

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

# run

The context object is available as a receiver (this). The return value is the lambda result. run does the same as with but invokes as let - as an extension function of the context object. run is useful when your lambda contains both the object initialization and the computation of the return value.

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
// the same code written with let() function:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + " to port ${it.port}")
}

# apply

The context object is available as a receiver (this). The return value is the object itself. Use apply for code blocks that don't return a value and mainly operate on the members of the receiver object. The common case for apply is the object configuration. Such calls can be read as “apply the following assignments to the object.”

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

Having the receiver as the return value, you can easily include apply into call chains for more complex processing.

# also

The context object is available as an argument (it). The return value is the object itself. also is good for performing some actions that take the context object as an argument. Use also for actions that need a reference to the object rather than its properties and functions, or when you don't want to shadow this reference from an outer scope. When you see also in the code, you can read it as “and also do the following with the object.”

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")

# Recursion

As a hybrid language, Kotlin supports a number of paradigms. Recursion is less likely than other languages, given that we have loops and other mechanisms to handle iteration.

However, the compiler certainly supports recursion, and can even optimize for tail recursion. To qualify, a function needs to:

  • be structured so that the last statement is a call to the function, with state being passed in the function call.
  • use the tailrec keyword.
import java.math.BigInteger

tailrec fun fibonacci(n: Int, a: BigInteger, b: BigInteger): BigInteger {
    return if (n == 0) a else fibonacci(n-1, b, a+b)
}

fun main(args: Array<String>) {
    println(fibonacci(100, BigInteger("0"), BigInteger("1")))
}
// 354224848179261915075

# Last Word