idiomatic-kotlin-course/notebooks/Idioms.ipynb
2024-12-08 12:38:44 +03:00

68 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 \$a")
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()
In [ ]:
var _a = 2
val a: Int get() = _a

println(a)
_a = 3
println(a)

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(4)
doSomethingWithDefault(2, "aaa")
doSomethingWithDefault(b = "fff", a = 8)
doSomethingWithDefault(6, 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) { // don't do that
    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

fun interface AnInterface {
    val a: Int
        get() = 4

    // set(value){
    //     println(value)
    // }
    fun doSomething() //= println("From interface")

    fun doSomething2() = Unit
}

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")
    override fun doSomething2(): Unit = TODO("Not yet implemented")
}

val anonymous2 = AnInterface {
    println("Do something")
}

/**
 * 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")
    }
}
In [ ]:
fun loop(block: () -> Unit): Nothing {
    while (true) {
        block()
    }
}

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 */) {
    init {
        require(b >= 0) { "B should be positive" }
    }

    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 [ ]:
import java.io.File

/**
 * 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 [ ]:
0.0..8.0 intersect 2.9..6.1
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"
)
val map2 = mutableMapOf(
    Pair("a", "a"),
    Pair("b", "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()) }

fun withoutAbuse(str: String?): Boolean? = if (str?.isNotEmpty() == true) {
    val substring = str.substring(0..4)
    substring.matches(".*".toRegex())
} else {
    null
}

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

fun incrementAndPring(arg: Int) = (arg + 1).also{
    println(it)
}

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)
In [ ]:

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") }

// java version
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 / Kotlin builder pattern

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)
}

/*inline*/ 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 }
    //.fold(0.0) { acc: Double, 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
}

private fun requestValue(): Int = TODO()

// This is the factory-function
fun Good(): 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()
println(lazyClass.lazyValue)
println(lazyClass.lazyValue)
println(lazyClass.getterValue)
println(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 [ ]: