idiomatic-kotlin-course/notebooks/Idioms.ipynb
2024-11-12 09:20:43 +03:00

66 KiB

Preamble/Important notes

  • "Syntactic sugar" is a false concept. Each language feature affects how we use the language.
  • One can adopt different programming styles in Kotlin. Idiomatic kotlin is an unofficial combination of practices developed by experienced Kotlin users.
  • One of the key features of idiomatic kotlin is to use scopes instead of objects to provide incapsulation and limit visible mutable state.

What is idiomatic Kotlin (roughly)?

  • Less "ceremony". Pragmatic approach.
  • Hybrid object/functional paradigm (objects for data, functions for interactions).
  • Don't use mutable state wherever read-only is enough.
  • Don't use classes when functions are enough.
  • Don't use inheritance when composition is enough.
  • Function is a first-class citizen.
  • Scope is a first-class citizen.
  • Use extensions to separate inner workings from utilities.

Do not do

  • Scope pollution.
  • Scope function abuse.
  • Rely on initialization order.

String interpolation

Interpolated string allows constructing string fast

In [ ]:
println("This is a plain string")
In [ ]:
val arg = "Interpolated"
println("This is an $arg string")
In [ ]:
val number = 1

fun increment(num: Int) = num + 1
println("This is an $arg string number ${increment(number) + 1}")
In [ ]:
println("This is a string with \$")
In [ ]:
println("""
    This is a 
        multi
            line
                raw
                    string
""".trimIndent())
In [ ]:
println("""This is a raw string number ${number+1} with \ and ${'$'} """)

Make val, not var!

val means read-only property

var means writable property

Unnecessary use of var is not recommended.

NOTE: In performance critical cases, explicit cycles are better, but mutable state should be encapsulated.

In [ ]:
/* Not recommended */
var counter = 0
for(i in 0..20){
    counter += i
}
counter
In [ ]:
/* recommended */
val sum = (0..20).sum()
sum
In [ ]:
val intArray: IntArray = IntArray(21){ it -> it + 1 }
intArray.sum()

Top level functions

Don't create a class when a function is enough.

Use access modifiers for incapsulation.

In [ ]:
/**
 * Functions could be defined everywhere in kotlin code. This is a so-called top-level function.
 */
fun doSomething(){
    println("I did it")
}

doSomething()
In [ ]:
interface AnInterface{
    fun doSomethingInAnObject()
}

object AnObject : AnInterface{
    /**
     * This is a member-function
     */
    override fun doSomethingInAnObject(){
        println("I did it in an object")
    }
}

AnObject.doSomethingInAnObject()
In [ ]:
fun doSomethingSpecial(){
    val base = "base"
    val special = "special"
    /**
     * This one is inside another function
     */
    fun String.doSomethingInside(){
        println("I did $special inside another function on $this")
    }
    base.doSomethingInside()
}
//"ddd".doSomethingInside()

doSomethingSpecial()
In [ ]:
fun returnFunction(): (String) -> Unit {
    fun printStringWithPrefix(str: String){
        println("Prefixed: $str")
    }
    return ::printStringWithPrefix
    //return { printStringWithPrefix(it) }
    // return { println("Prefixed: $it") }
}

returnFunction()("Haskel, Boo!")

Unit

In [ ]:
fun returnUnit(): Unit {
    // no return statement `Unit` is returned implicitly
    val b = Unit
}
In [ ]:
fun doNothing(){
    
}

val a = doNothing()
a::class

Default parameters

In [ ]:
fun doSomethingWithDefault(a: Int = 0, b: String = "") {
    println("A string with a == $a and b == \"$b\"")
}

doSomethingWithDefault()
doSomethingWithDefault(2, "aaa")
doSomethingWithDefault(b = "fff", a = 8)
doSomethingWithDefault(4, b = "fff")
doSomethingWithDefault(a = 2, "fff")// don't do that
In [ ]:
fun integrate(
    from: Double = 0.0,
    to: Double = 1.0,
    step: Double = 0.01,
    function: (Double) -> Double,
): Double {
    require(to > from)
    require(step > 0)
    var sum = 0.0
    var pos = from
    while (pos < to) {
        sum += function(pos)
        pos += step
    }
    return sum*step
}

integrate { x ->
    x * x + 2 * x + 1
}
integrate(0.0, PI) { sin(it) }
// integrate(0.0, PI)  //error
integrate(0.0, step = 0.02, to = PI) { sin(it) }
//integrate(0.0, step = 0.02, PI) { sin(it) }

integrate(0.0, step = 0.02, function = { sin(it) }, to = PI)
In [ ]:
fun functionWithParameters(){
    println("without default")
}

//@JvmOverloads
@JvmName("functionWithDefaultParameters")
fun functionWithParameters(a: Int = 2){
    println("with default")
}

functionWithParameters()

Function body shortcut

In [ ]:
fun theSameAsBefore(a: Int = 0, b: String = ""): String = "A string with a == $a and b == $b"

class Context(val a: String, val b: String)

fun somethingWith(context: Context) = with(context){
    println(a + b)
}

Trailing lambda

Scope is a separate important concept in Kotlin

In [ ]:
data class BuildResult internal constructor(val str: String, val int: Int, val optional: Double?){
    init{
        require(optional!= null){ "null!" }
    }
}

class Builder(
    var str: String = "", 
    var int: Int = 0
){
    var optional: Double? = null

    fun build() = BuildResult(str, int, optional)
}

fun BuildResult(defaultInt: Int, block: Builder.() -> Unit): BuildResult = 
    Builder(int = defaultInt).apply(block).build()

val res = BuildResult(2){
    str = "ff"
    optional = 1.0
}

res

Inheritance

In [ ]:
//Interfaces and objects

interface AnInterface {
    val a: Int
        get() = 4
        // set(value){
        //     println(value)
        // }
    fun doSomething() //= println("From interface")
}

abstract class AnAbstractClass(override val a: Int): AnInterface{
    override final fun doSomething() = println("From a class")
    abstract fun doSomethingElse()
}

class AClass(override val a: Int) : AnInterface {
    override fun doSomething() = println("From a class")
}

class BClass(a: Int) : AnAbstractClass(a) {
    override fun doSomethingElse() = println("something else")
}

/**
 * A singleton (object) is a type (class) which have only one instance
 */
object AnObject : AnInterface {
    override fun doSomething(): Unit = TODO("Not yet implemented")
}

/**
* Creating an instance
*/
val instance = AClass(3)

/**
* Using singleton reference without constructor invocation
*/
val obj: AnInterface = AnObject

/**
* Anonymous object
*/
val anonymous = object : AnInterface {
    override fun doSomething(): Unit = TODO("Not yet implemented")
}

/**
* The one that should not be named
*/
val voldemort = object {
    fun doSomething(): Unit = TODO()
}

// voldemort.doSomething()
In [ ]:
fun interface Float64Function: (Double) -> Double {
    override operator fun invoke(arg: Double): Double
    
    operator fun invoke(doubles: DoubleArray): DoubleArray = DoubleArray(doubles.size){ invoke( doubles[it]) }
}

val sin = object : Float64Function{
    override fun invoke(p1: Double): Double = sin(p1)
}

val cos = Float64Function{ kotlin.math.cos(it) }

val tg = Float64Function(::tan)

sin(PI/2)

sin(doubleArrayOf(0.0, PI/2))

Declaration site variance

In consumes, out produces.

In [ ]:
interface Producer<out T>{
    fun produce(): T
}

interface Consumer<in T>{
    fun consume(value: T)
}

interface Doer<T>: Producer<T>, Consumer<T> {

}
In [ ]:
fun transform(color: String): Int = when (color) {
    "Red" -> 0
    "Green" -> 1
    "Blue" -> 2
    else -> throw IllegalArgumentException("Invalid color param value")
}
In [ ]:
fun transformIf(flag: Boolean): Int = if (flag) 1 else 0

Runtime type dispatch

In Java it is called DOP (Data Oriented Programming).

Also, sometimes (wrongly) called pattern matching.

In [ ]:
/**
 * Matching by type
 */
fun checkType(arg: Any): Unit = when (arg) {
    is String -> println("I am a String. Length is ${arg.length}")
    is Int -> println("I am an Int.")
    is Double -> println("I am a Double")
    // 2==2 -> println("Wat?")
    else -> println("I don't know who am I?")
}

fun checkType2(arg: Any): Unit = when {
    arg is String -> println("I am a String")
    arg is Int -> println("I am an Int")
    arg is Double -> println("I am a Double")
    2 == 2 -> println("Wat?")
    else -> println("I don't know who am I?")
}

checkType(true)
In [ ]:
fun tryCatch() {
    val result: Int = try {
        22
    } catch (e: ArithmeticException) {
        throw IllegalStateException(e)
    } finally {
        println("finally")
    }

    // Working with result
}
In [ ]:
val res: Result<Int> = runCatching<Int> { 
    //error("Error happened")
    4
}

println(res.exceptionOrNull())
(res.getOrNull() ?: 0) + 1
In [ ]:
val dataFlow = sequence {
    var i = 0
    while (true) {
        yield(i++)
    }
}

dataFlow.map {
    runCatching {
        if (it % 2 == 0) error("Even: $it") else it
    }
}.take(10).toList().joinToString(separator = "\n")

Nothing

In [ ]:
/**
 * The declaration if valid because [TODO] returns Nothing
 */
fun getSomething(): Int? = TODO()

fun findSomething(): Int {
    // early return is valid because `return` operator result is Nothing
    val found: Int = getSomething() ?: return 2
    return found + 2
}

//FIXME don't do that
fun dontDoThat() {
    if (true) return
    println(false)
}

fun checkCondition(): Int {
    fun conditionSatisfied() = false

    return if (conditionSatisfied()) {
        1
    } else {
        //error is Nothing
        error("Condition is not satisfied")
    }
}

Data class

In [ ]:
class SimpleClass(val a: Int, val b: Double, c: Double){
    override fun toString() = "SimpleClass(a=$a, b=$b)"
}


data class DataClass(val a: Int, var b: Double /*, d: Double */) {
    val c get() = b + 1
}

val simple = SimpleClass(1, 2.0, 3.0)

println(simple)

val data = DataClass(2, 2.0)
val copy = data.copy(b = 22.2)
println(copy)

Nullable truth

In [ ]:
/**
 * Nullable truth.
 */
fun nullableTruth() {
    class A(val b: Boolean?)

    val a: A? = A(null) // Compilator warning here
    //use
    a?.b == true
    //instead of
    a?.b ?: false

    // The old way
    val res = if (a == null) {
        false
    } else {
        if (a.b == null) {
            false
        } else {
            a.b
        }
    }
}
In [ ]:
import java.util.Optional
import kotlin.reflect.typeOf

println(typeOf<Optional<Boolean>>()  == typeOf<Optional<Optional<Boolean>>>())

println( typeOf<Boolean?>() == typeOf<Boolean??>())
In [ ]:
mapOf<String,String>().get("f")!!

Safe call

In [ ]:
/**
 * Safe call and elvis operator
 */
fun idiom12() {
    val files = File("Test").listFiles()
    println(files?.size ?: "empty")
}
idiom12()
In [ ]:
fun printNotNull(any: Any) = println(any)

// printNotNull(null) does not work

val value: Int? = 2
//val value: Int? by lazy { 2 }

//printNotNull(value) // Error
if (value != null) {
    //not guaranteed to work with mutable variable
    printNotNull(value)
}

var value1: Int? = 2

// Safe call here
value1?.let {
    printNotNull(it) // execute this block if not null
    //printNotNull(value1) // value is not null here
}

Nullable assignment

In [ ]:
/**
 * Dart-like (?=) nullable assignment
 */
fun idiom11() {
    var x: String? = null
    x = x ?: "Hello"
}

Extension functions

In [ ]:
/**
 * Extension function on a String. It is not monkey-patching.
 */
fun String.countOs(): Int = count { it == 'ы' } // implicit this points to String

fun Int.printMe() = println(this) // explicit this

fun Any?.doSomething(): Int = TODO("Don't do that")

fun ((Int) -> Int).evalAndIncrement(): (Int) -> Int = { this.invoke(it) + 1 }

// val intFunction: (Int)->Int = {it -1}

// val modifiedFunction = intFunction.evalAndIncrement()

// println(modifiedFunction(0))

"вылысыпыдыстычка".countOs().printMe()
In [ ]:
infix fun <T: Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): ClosedRange<T>?{
    val start = maxOf(this.start, other.start)
    val end = minOf(this.endInclusive, other.endInclusive)
    return if(end>=start) start..end else null
}

(0..8).intersect(6..12)
0..8 intersect 6..12
In [ ]:

In [ ]:
fun List<String>.concat() = joinToString(separator = "")
listOf("a","b","c").concat()
listOf(1,2,3).concat()
In [ ]:

In [ ]:
val functionWithReciever: List<String>.(arg: Int) -> Unit = {arg->
    println(get(arg))
}

listOf("1", "2", "3").functionWithReciever(1)

Extension properties

Remember property usage guidelines.

In [ ]:
/**
 * Extension property (must be virtual)
 */
val List<Number>.odd get() = filter { it.toInt() % 2 == 1 }

List(10){it}.odd
In [ ]:
var MutableMap<String, String>.a: String?
    get() = this.get("a")
    set(value){
        if(value == null){
            this.remove("a")
        } else {
            this.set("a",value)
        }
    }

val map = mutableMapOf("a" to "a", "b" to "b")

map.a = "6"

map.a
In [ ]:
/**
 * Extension variable (also must be virtual)
 */
var Array<Int>.second: Int
    get() = this[1]
    set(value) {
        this[1] = value
    }


val array = Array(5){it}
array.second = 9
array

Scope functions (run, with, let)

In [ ]:
object AClass{
    val a = "a"
    val b = "b"
    val c = "c"
}

fun getAClass(): AClass? = null

/**
 * [with]/[run]/[let] are used to create a scope with given argument or receiver
 */


// Simple print of AClass
println("a = ${AClass.a}, b = ${AClass.b}, c = ${AClass.c}")

// Using `with`
val res = with(AClass){
    // AClass type is the receiver in this scope
    println("a = ${this.a}, b = $b, c = $c")
    /*return@with*/ "some value"
}

res
In [ ]:
object AContext{
    fun AClass.abc() = a + b + c // warning additional concatenation
}

fun printAbc(aClass: AClass) = with(AContext){
    aClass.abc()
}
In [ ]:
//using `run`
getAClass()?.takeIf { it.a.isNotEmpty() }?.run {
    // the same as above
    println("a = $a, b= $b, c = $c")
    2
}

val runResult: Int = run {
    
}

//Using `let` to compose result. Not recommended using without a need
val letResult = getAClass()?.let { arg ->
    arg.c + arg.a
}
In [ ]:
//Don't do that
fun scopeAbuse(str: String?) = str.takeIf { it?.isNotEmpty() == true }?.let { it.substring(0..4) }?.let { it.matches(".*".toRegex())}

Scope functions (also, apply)

In [ ]:
/**
 * Using apply/also to add a finalizing action
 */
var i = 2

/**
* [also] block does not affect the result
*/
fun getAndIncrement() = i.also { i += 1 } //don't do that

println(getAndIncrement())
println(i)
In [ ]:
class Rectangle {
    var length: Number = 0
    var breadth: Number = 0
    var color: Int = 0xffffff
}

/**
    * Configure properties of an object (apply)
    * https://kotlinlang.org/docs/idioms.html#configure-properties-of-an-object-apply
    */
val myRectangle = Rectangle().apply {
    length = 4
    breadth = 5
    color = 0xFAFAFA
}

fun Rectangle(block: Rectangle.() -> Unit): Rectangle = Rectangle().apply(block)

val myRectangle2 = Rectangle {
    length = 4
    breadth = 5
    color = 0xFAFAFA
}

fun Rectangle(length: Number) = Rectangle().apply { this.length = length }

Rectangle(8)

Lists

Do not cast List to MutableList!

In [ ]:
/**
 * Lists and mutable lists
 */


/**
 * This method creates a read-only list of strings. One should note that the type of the list is not specified
 */
val list: List<String> = listOf("a", "b", "c")

println(list[0])
println(list.get(1))
//println(list.get(4))
println(list.getOrNull(4) ?: "nothing")
list::class
In [ ]:
/**
* This one creates a mutable list
*/
val mutableList: MutableList<String> = mutableListOf("a", "b", "c")
mutableList[2] = "d"
mutableList.add("e")
mutableList += "f"
mutableList
In [ ]:
//don't do that ever
fun doBadThingWithList(list: List<String>): List<String> = (list as MutableList<String>).apply { add("something") }

doBadThingWithList(listOf("a", "b", "c"))
In [ ]:
listOf("a")::class
In [ ]:
val mutableList = mutableListOf(1,2,3)

fun consumeList(list: List<Int>){
    println(list.joinToString())
}
consumeList(mutableList)
mutableList.add(4)
consumeList(mutableList)
In [ ]:
listOf(1,2) + listOf(3,4)
In [ ]:
var roList = listOf("a", "b")
roList += setOf("c")
roList
In [ ]:
val writableList = mutableListOf("a", "b") // Warning!
writableList += setOf("c")
writableList
In [ ]:
/**
 * This one creates a mutable ArrayList.
 */
val arrayList: ArrayList<String> = arrayListOf("a", "b", "c")

//ArrayList<Int>()

//Danger zone
//
//val newList: List<String> = list + "f" + mutableList
//
//println(newList)
In [ ]:
//Bonus

val lambdaList = List(3){ it.toString() }
println(lambdaList)

val builderList: List<Int> = buildList {
    add(2)
    add(8)
    remove(8)
}
builderList
In [ ]:
// val sequentialList = List(20){it}
// sequentialList.zipWithNext{ l, r -> r - l }.forEach{println(it)}

Shortcut collection builders

In [ ]:
/**
 * Use shortcut function to provide default values
 */
fun doSomething(additionalArguments: List<String> = emptyList()){
    TODO()
    emptyArray<String>()
    emptySet<String>()
    emptyMap<String,String>()
}
emptyList<String>()::class
In [ ]:

Maps

In [ ]:
val map = mutableMapOf(
    "key" to "a",
    "key2" to "b",
)

//The map could be accessed via indexing operation
println(map::class)
println(map["key"])
map["key"] = "fff"
println(map["key"])
println(map["key3"])
In [ ]:
//val entry: MutableMap.MutableEntry<String, String> = map.iterator().next()

//map.entries.first().component2()

/**
    * The destructuring declaration for maps and other objects that support `operator fun componentN`
    */
for ((k: String, v) in map) {
//val (k, v) = entry
//        val k = entry.component1()
//        val v = entry.component2()
    println("$k -> $v")
}
In [ ]:
map.forEach { (k, v) -> println("$k -> $v")}
In [ ]:
val (a, b) = Pair(1, 2)

val coord = doubleArrayOf(0.0, 1.0, 2.0)

val (x,y,z) = coord

data class Coordinate(val x: Double, val y: Int)

val (x1, y1) = Coordinate(1.0, 2)

Mutable type access decorator

To be replaced with qualified getter types soon

In [ ]:
class AClassWithList{
    var b: Double = 2.0
        private set

    init{
        b = 5.0
    }

    private val _list: MutableList<Int> = ArrayList<Int>()
    val list: List<Int> get() = _list
    
    fun add(int: Int){
        require(int>0)
        _list.add(int)
    }
}



val obj = AClassWithList()

// obj.b = 10.0 //error
obj.add(42)
obj.list.add(43)

Wrap mutable logic

In [ ]:
val list = buildList {
    repeat(10){
        add(it)
    }
}
println(list)
In [ ]:
data class ImmutableObject(val a: String, val b: String, val c: Int)

class ImmutableObjectBuilder(a: String) {
    var a: String = a
    var b: String = ""
    var c = 0
    
    fun build() = ImmutableObject(a, b, c)
}

fun ImmutableObject(a: String, block: ImmutableObjectBuilder.() -> Unit): ImmutableObject =
    ImmutableObjectBuilder(a).apply(block).build()

ImmutableObject("aValue") {
    c = 99
}

Contains operator and ranges

In [ ]:
val emailsList = emptyList<String>()

// When used directly infix in operator checks if the element is contained in a collection
//using `operator fun contains`

if ("john@example.com" in emailsList) {
    println("is in list")
}

if ("jane@example.com" !in emailsList) {
    println("not in list")
}
In [ ]:
import java.time.*

class DateRange(val start: Instant, val end: Instant)

operator fun DateRange.contains(value: Instant): Boolean = value > start && value < end


println(Instant.now() in DateRange(Instant.EPOCH, Instant.MAX))
In [ ]:
// Another (different) use of `in` is iteration over range or collection using
// using `operator fun iterator`

for (i in 1..100) {
    println(i)
}  // closed range: includes 100

(1..100).forEach { i ->
    println(i)
} //the same, but with boxing

for (i in 1..<100) {
    println(i)
} // half-open range: does not include 100

for (x in 2..10 step 2) {
    println(x)
}

for (x in 10 downTo 1) {
    println(x)
}

infix fun ClosedRange<Double>.step(step: Double): Sequence<Double> {
    //TODO check arguments
    var current = start
    return sequence {
        do {
            yield(current)
            current += step
        } while (current <= endInclusive)
    }
}

for (x in 0.0..10.0 step 0.5){
    println(x)
}

Map-reduce

In [ ]:
val list: List<Int> = listOf(1, 2, 3, 4, 5, 6)

val result = list
    //.stream().parallel()
    //.asSequence()
    .filter { it % 2 == 0 } //select even numbers
    .map { it * it } // get square of each element
    //.onEach { println(it) }
    //.sumOf { it } //use one of reduce operations
    .reduce { acc: Int, i: Int -> acc + i }

result
In [ ]:
val sequence = sequence {
    var counter = 1
    while(true){
        yield(counter++)
        yield(counter++)
        // println(counter)
    }
}

val result = sequence
    .take(6)
    .filter { it % 2 == 0 } //select even numbers
    .map { it * it } // get square of each element
    //.onEach { println(it) }
    //.sumOf { it } //use one of reduce operations
    .reduce { acc: Int, i: Int -> acc + i }

result

Scoped resource usage

In [ ]:
import java.nio.file.*


val stream = Files.newInputStream(Paths.get("file.txt"))
// The resource is automatically closed  when leaving the scope
stream.bufferedReader().use { reader ->
    println(reader.readText())
}

Factory as parameter and companion factories

In [ ]:
interface Factory<T : Any> {
    fun build(str: String): T
}

data class IntContainer(val arg: Int) {
    
    companion object : Factory<IntContainer> {
        override fun build(str: String) = IntContainer(str.toInt())

        fun buildSpecial(str: String) = IntContainer(str.toInt())
    }
}

data class DoubleContainer(val arg: Double) {
    
    companion object : Factory<DoubleContainer> {
        override fun build(str: String) = DoubleContainer(str.toDouble())
    }
}

fun <T : Any> buildContainer(str: String, factory: Factory<T>): T = factory.build(str)


buildContainer("22", IntContainer)

Initialization

In [ ]:
open class Bad{
    val value: Int = requestValue()

    open fun requestValue(): Int {
        doSomethingElse()
        return 2
    }

    private fun doSomethingElse(){
        println(value)
    }
}

Bad()
In [ ]:
class BadString{
    val value: String = requestValue()

    fun requestValue(): String {
        doSomethingElse()
        return "2"
    }

    private fun doSomethingElse(){
        println(value)
    }
}

BadString()
In [ ]:
//Factory functions are preferred to the initialization logic

data class Good internal constructor(val value: Int){
    init {
        //Initialization block is there to check arguments
        require(value >= 0)
    }

    companion object
}

fun requestValue(): Int = TODO()

// This is the factory-function
fun Good() = Good(requestValue())


// additional constructor-like builders could be added to the companion

@OptIn(ExperimentalUnsignedTypes::class)
fun Good.Companion.build(value: UInt) = Good(value.toInt())

Good.build(32U)

Delegates

In [ ]:
class ClassWithALazyProperty{
    //Use lazy delegate for something that should be calculated ones on first call
    val lazyValue by lazy {
        //Do dome heavy logic here
        println("Initialized")
        22
    }

    val getterValue: Int get(){
        println("got")
        return 33
    }
}
val lazyClass = ClassWithALazyProperty()
lazyClass.lazyValue
lazyClass.lazyValue
lazyClass.getterValue
lazyClass.getterValue
In [ ]:
//Using other delegates
val map = mutableMapOf("a" to 1, "b" to 2)

var a: Int by map

println(a)
a = 3
println(map)

Inline functions

In [ ]:
/**
 * Definition of inline function
 */
inline fun List<Int>.forEachOdd(block: (Int) -> Unit) = forEach {
    if (it % 2 == 1) block(it)
}


/**
 * The demonstration of use of inline [forEach] function with non-local return
 */
fun foo(): Int {
    listOf(1, 2, 3, 4, 5).forEachOdd {
        if (it == 3) return it // non-local return directly to the caller of foo()
        print("$it, ")
    }
    println("this point is unreachable")
    return 0
}
foo()
In [ ]:
/**
 * Using inline function for type reification during the compile time
 */
inline fun <reified T> List<T>.prettyPrint() = forEach {
    when (T::class) {
        Double::class -> println("Double: ${it as Double}")
        Int::class -> println("Int: ${it as Int}")
        else -> it.toString()
    }
}

inline fun <reified T> prettyPrintOne(arg: T) = listOf(arg).prettyPrint()

listOf(1, 2, 3).prettyPrint()

/**
 * **WARNING** inline functions are an advanced feature and should be used only for
 * reification or non-local return
 * NOT for optimization.
 */

Collections and boxing

In [ ]:
/**
 * Values are boxed. Each call is indirect
 */
val list: List<Double> = List(20) { it.toDouble() }

/**
 * Still boxed
 */
val genericArray: Array<Double> = Array(20) { it.toDouble() }

/**
 * Non-boxed
 */
val specializedArray: DoubleArray = DoubleArray(20) { it.toDouble() }
In [ ]: