joker-kotlin-idiomatic/Idioms.ipynb

48 KiB
Raw Permalink Blame History

Idiom 1, 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
println("This is an $arg string number ${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 ${'$'} """)

Idiom 2, val VS 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 }
intArray.sum()

Idiom 3 top level functions

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 [ ]:
object AnObject{
    /**
     * This is a member-function
     */
    fun doSomethingInAnObject(){
        println("I did it in an object")
    }
}

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

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

returnFunction()("Haskel, Boo!")
In [ ]:
//idiom 3 - Unit
fun returnUnit(): Unit{
    // no return statement `Unit` is returned implicitly
}
In [ ]:
//idiom 4 - default parameters
fun doSomethingWithDefault(a: Int = 0, b: String = ""): String {
    return "A string with a == $a and b == \"$b\""
}

doSomethingWithDefault(4, b = "fff")
In [ ]:
//idiom 5 - function body shortcut
fun theSameAsBefore(a: Int = 0, b: String = ""): String = "A string with a == $a and b == $b"

Idiom 6

In [ ]:
//Interfaces and objects

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

class AClass(override val a: Int) : AnInterface {
    override fun doSomething() = TODO()
}

/**
 * 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()

Idiom 7

Runtime type dispatch

In [ ]:
/**
 * Matching by type
 */
fun checkType(arg: Any): Unit = when(arg){
    is String -> println("I am a String")
    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?")
}

checkType2(true)

Idiom 8

In [ ]:
class SimpleClass(val a: Int, val b: Double, c: Double)


data class DataClass(val a: Int, val b: 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)

Idiom 9

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 = getSomething() ?: return 2
    return found + 2
}

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

    return if (conditionSatisfied()) {
        1
    } else {
        //error is Nothing
        error("Condition is not satisfied")
    }
}
In [ ]:
/**
 * idiom 10
 * Nullable truth.
 */
fun idiom10() {
    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 [ ]:
/**
 * Dart-like (?=) nullable assignment
 */
fun idiom11() {
    var x: String? = null
    x = x ?: "Hello"
}
In [ ]:
/**
 * Safe call and elvis operator
 */
fun idiom12() {
    val files = File("Test").listFiles()
    println(files?.size ?: "empty")
}
idiom12()

Idiom 13

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
}

Idiom 14: 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

"вылысыпыдыстычка".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

15

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

/**
 * 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 = $a, b = $b, c = $c")
    return@with "some value"
}

res
In [ ]:
//using `run`
getAClass()?.run {
    // the same as above
    println("a = $a, b= $b, c = $c")
}

//Using `let` to compose result. Not recommended using without a need
val letResult = getAClass()?.let { arg ->
    arg.c + arg.a
}

Scope functions (also, apply)

In [ ]:
class Rectangle{
    var length: Number = 0
    var breadth: Number=0
    var color: Int = 0xffffff
}

/**
 * Using apply/also to add a finalizing action
 */
var i = 2

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

println(getAndIncrement())
println(i)

/**
    * 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
}
In [ ]:
class A() {
  inner class B{
      fun doSomething(){
          println(this)
          println(this@A)
      }
  }
}
A().B().doSomething()
In [ ]:

Idiom 18 lists

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 = listOf("a", "b", "c")

println(list[0])
println(list.get(1))
println(list.getOrNull(4) ?: "nothing")
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 [ ]:
/**
    * This one creates a mutable ArrayList.
    */
val arrayList: ArrayList<String> = arrayListOf("a", "b", "c")

//Danger zone

val newList: List<String> = list + "f" + mutableList

println(newList)
In [ ]:
//Bonus

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


val builderList = buildList{
    add(2)
    add(8)
    remove(8)
}
builderList
In [ ]:
// val sequentialList = List(20){it}
// sequentialList.zipWithNext{ l, r -> r - l }.forEach{println(it)}
In [ ]:
/**
 * idiom 19
 * Use shortcut function to provide default values
 */
fun doSomething(additionalArguments: List<String> = emptyList()){
    TODO()
    emptyArray<String>()
    emptySet<String>()
    emptyMap<String,String>()
}

Idiom 20

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

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

/**
    * The destructuring declaration for maps and other objects that support `operator fun componentN`
    */
for ((k, 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)

Idiom 22 Mutable type access decorator

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
}



val obj = AClassWithList()

// obj.b = 10.0 //error
obj.list

Idiom 23

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

Idiom 25 default arguments

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, step = 0.02, to = PI) { sin(it) }
//integrate(0.0, step = 0.02, PI) { sin(it) }

Idiom 26 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

Idiom 28 Wrap mutable logic

In [ ]:
val list = buildList {
    repeat(10){
        add(it)
    }
}
println(list)

Idiom 29 resource usage

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

Idiom 30 Factory as parameter and companion factories

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

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

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)

Idiom 31 initialization

In [ ]:
open class Bad{
    val value: Int

    init {
        value = requestValue()
    }

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

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

Bad()
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)

Idiom 32 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 = mapOf("a" to 1, "b" to 2)

val a: Int by map

println(a)

Idiom 33 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() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // non-local return directly to the caller of foo()
        print("$it, ")
    }
    println("this point is unreachable")
}
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()

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

Idiom 34 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 [ ]: