## Chapter 7: Make it cheap ### Effective Kotlin --- ## Who am I? - [Brandy :arrow_right: Github](https://github.com/hmchangm/getting-start-QK) - [2022 鐵人賽 Quarkus, Kotlin 雲原生服務開發](https://ithelp.ithome.com.tw/users/20135701/ironman/5479) --- ### Type Safty :rocket: ### Make it Cheap :moneybag: --- ## Item 49: Inline Value Classes ---- ### Domain Model with Primitive Type? ![](https://i.imgur.com/rl4APCM.png) ---- ## Some Cases ```kotlin= fun auth(userName: String, password: String) { println("authenticating $userName.") } fun main() { auth("user1", "12345") //Compilable, thay are all String auth("12345", "user1") } ``` ---- ## Inline Value Classes ```kotlin= @JvmInline value class Password(val value: String) @JvmInline value class UserName(val value: String) ``` ---- ```kotlin= fun auth(userName: UserName, password: Password) { println("authenticating $userName.") } fun main() { auth(UserName("user1"), Password("12345")) //does not compile due to type mismatch auth(Password("12345"), UserName("user1")) } ``` ##### [Reference Link](https://www.droidcon.com/2022/09/25/my-top-4-use-cases-for-kotlin-inline-classes/) ---- ### 火星氣候探測者號 #### [1 牛頓力 != 1 磅](https://kt.academy/article/ek-value-classes) ![](https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Mars_Climate_Orbiter_2.jpg/435px-Mars_Climate_Orbiter_2.jpg) > 327.6 million USD ---- ### 編譯期優化 還原型別 ![](https://cdn2.ettoday.net/images/2333/2333461.gif) ---- #### 型別安全 = Cost Saving ![](https://i.imgur.com/0P3HMJM.png) --- ### Item 39 : Use sealed classes and interfaces to express restricted hierarchies ---- ```kotlin= sealed interface BinaryTree class Leaf(val value: Any?) : BinaryTree class Node(val left: Tree, val right: Tree) : BinaryTree sealed interface Either<out L, out R> class Left<L>(val value: L) : Either<L, Nothing> class Right<R>(val value: R) : Either<Nothing, R> ``` ---- ### Sealed Classes Sum Type :arrow_right: 可以被窮舉 ![](https://i.imgur.com/rFn4r3x.png) ---- ![](https://i.imgur.com/fwPEa4R.png) ---- ### 優雅的 if else ```kotlin= val x : Either<Exception, String> = magic("2") val value = when(x) { is Either.Left -> when (x.value) { is NumberFormatException -> "Not a number!" is IllegalArgumentException -> "Can't take 0!" } is Either.Right -> "Got reciprocal: ${x.value}" } ``` --- [2020 鐵人賽 Algebraic Data Type (ADT) by Yanbin](https://ithelp.ithome.com.tw/articles/10240987) --- ### Make it cheap * High performance * Memory comsumption * Cost Saving --- #### Item 47: Avoid unnecessary object creation ##### Memory Cost :moneybag: * Object Self * Object header : 16 bytes * Object reference : 4 bytes --- ```kotlin= class A private val a = A() // Benchmark result: 2.698 ns/op fun accessA(blackhole: Blackhole) { blackhole.consume(a) } // Benchmark result: 3.814 ns/op fun createA(blackhole: Blackhole) { blackhole.consume(A()) } ``` ---- ```kotlin= // Benchmark result: 3828.540 ns/op fun createListAccessA(blackhole: Blackhole) { blackhole.consume(List(1000) { a }) } // Benchmark result: 5322.857 ns/op fun createListCreateA(blackhole: Blackhole) { blackhole.consume(List(1000) { A() }) } ``` --- ### Tips 1 : Singlton - Object Reuse ---- ```kotlin= sealed class LinkedList<T> class Node<T>(val head: T,val tail: LinkedList<T>) : LinkedList<T>() class Empty<T> : LinkedList<T>() // Usage val list: LinkedList<Int> = Node(1, Node(2, Node(3, Empty()))) val list2: LinkedList<String> = Node("A", Node("B", Empty())) ``` ---- ```kotlin= sealed class LinkedList<out T> class Node<out T>( val head: T, val tail: LinkedList<T> ) : LinkedList<T>() object Empty : LinkedList<Nothing>() val list: LinkedList<Int> = Node(1, Node(2, Node(3, Empty))) val list2: LinkedList<String> = Node("A", Node("B", Empty)) ``` ---- ```kotlin= sealed interface AdView object FacebookAd : AdView object GoogleAd : AdView class OwnAd(val text: String,val imgUrl: String):AdView ``` --- #### Tips#2 - Cache ```kotlin= private val FIB_CACHE: MutableMap<Int, BigInteger> = mutableMapOf<Int, BigInteger>() fun fib(n: Int): BigInteger = FIB_CACHE.getOrPut(n) { if (n <= 1) BigInteger.ONE else fib(n - 1) + fib(n - 2) } ``` ---- * Designing a cache well is not easy * **Caffeine** and **Ehcache**, * waiting for **support for suspending functions** ---- #### [Aedile](https://github.com/sksamuel/aedile) support suspending function ##### Aedile is a simple Kotlin wrapper for Caffeine which prefers coroutines ---- #### Cache RESTful Result ```kotlin= val cache = caffeineBuilder<String, FruityVice> { expireAfterWrite = 5.toDuration(DurationUnit.MINUTES) }.build { findByName(it) } // expensive call private suspend fun findByName(name: String) = suspend fun findByNameWithCache(name: String) = cache.get(name) ``` --- #### Tips#3 - Heavy object lifting ```kotlin= fun <T : Comparable<T>> Iterable<T>.countMax(): Int = count { it == this.maxOrNull() } fun <T : Comparable<T>> Iterable<T>.countMax(): Int { val max = this.maxOrNull() return count { it == max } } ``` ---- ```kotlin= fun String.isValidIpAddress(): Boolean { return this.matches( ("""\A(?:(?:25[0-5]|2[0-4][0-9]| [01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]| [01]?[0-9][0-9]?)\z""").toRegex() ) } // Usage print("5.173.80.254".isValidIpAddress()) // true ``` ---- ##### Lift out ```kotlin= private val IS_VALID_IP_REGEX = """\A(?:(?:25[0-5]|2[0-4][0-9]| [01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]| [01]?[0-9][0-9]?)\z""".toRegex() fun String.isValidIpAddress(): Boolean = matches(IS_VALID_IP_REGEX) ``` --- #### Tips4 Lazy `lazy()` is a function that takes a lambda and returns an instance of `Lazy<T>` The **first** call to get() executes the lambda passed to lazy() and remembers the result. ---- ```kotlin= object { val objectMapper: ObjectMapper by lazy { Arc.container() .instance(ObjectMapper::class.java).get() } fun Any.toJson() : String = objectMapper.writeValueAsString(this) } ``` [Quarkus Sample](https://github.com/hmchangm/getting-start-QK/blob/master/src/main/kotlin/tw/brandy/ironman/resource/ResponseHandler.kt) --- ### Item 48 : Use inline modifier for functions --- ##### Inline function nearly all Kotlin stdlib **higher-order functions** have an inline modifier ```kotlin= inline fun repeat(times: Int,action: (Int) -> Unit) { for (index in 0 until times) { action(index) } } ``` ---- ```kotlin= repeat(10) { print(it) } //after compilation for (index in 0 until 10) { print(index) } ``` --- #### type argument can be reified ```kotlin= fun <T> printTypeName() { print(T::class.simpleName) // ERROR } ``` ##### JVM erased generic types during compilation ![](https://i.imgur.com/LWpSaQQ.png) ---- #### inline + reified ```kotlin= inline fun <reified T> printTypeName() { print(T::class.simpleName) } // Usage printTypeName<Int>() // Int printTypeName<Char>() // Char printTypeName<String>() // String ``` --- ### reified with JsonDecode ```kotlin inline fun <reified T> String.toType():Either<AppError, T>= Either.catch { objectMapper.readValue(this, T::class.java) }.mapLeft { JsonDecodeFail(it) } val user: Either<AppError,User> = userText.toType() ``` --- ##### High order functions faster when inlined 違背了哪一條? ```kotlin= val lambda: () -> Unit = { // code } ``` ```java= // compiled to JVM equivalent of Function0<Unit> lambda = new Function0<Unit>() { public Unit invoke() { // code } }; ``` --- ### crossinline 與 noinline * noinline - the λ argument should not be inlined * crossinline - the λ argument should be inlined but non-local return is not allowed ---- ```kotlin= inline fun requestNewToken( hasToken: Boolean, crossinline onRefresh: () -> Unit, noinline onGenerate: () -> Unit ) { if (hasToken) { httpCall("get-token", onGenerate) } else { httpCall("refresh-token") { onRefresh() onGenerate() } } } ``` ---- ![](https://i.imgur.com/EggJwia.png) ---- ![](https://i.imgur.com/ZQA0t7T.png) ---- #### noinline, crossinline → IntelliJ 會告訴你 ---- #### 沒有 function type 的參數的 fun 不需要 inline ![](https://i.imgur.com/69x5pVC.png) --- #### inline fun 時機 * 很常很常用的 function * reified type function * High ordered functoin --- {%youtube RM7aulixXC8 %} --- #### Item 50: Eliminate obsolete object references ```kotlin= fun pop(): Any? { if (size == 0) throw EmptyStackException() val elem = elements[--size] elements[size] = null ← 避免孤兒 return elem } ``` --- ![](https://i.imgur.com/gpG4LxI.png) --- ![](https://i.imgur.com/Hk6go5W.png) --- ### Take Away * Type Safe Domain Model * Data Class * Sealed Class (Interface) * Inline Value Class ---- * Make it cheap * 空間 - Avoid necessary object creation * 時間 - Cache * GC friedly --- ### 參考連結 [Kotlin Inline Value Class 與 Jackson Json Serialization](https://ithelp.ithome.com.tw/articles/10309687) https://www.youtube.com/watch?v=0JRPA0tt9og https://www.youtube.com/watch?v=SIr7mcnVy98
{"metaMigratedAt":"2023-06-17T14:05:59.255Z","metaMigratedFrom":"YAML","title":"Chapter 7 - Make it cheap","breaks":true,"description":"View the slide with \"Slide Mode\".","contributors":"[{\"id\":\"28aef4d3-277e-46c5-887c-233edfe0da9f\",\"add\":15117,\"del\":5834}]"}
    740 views