Skip to content

๐Ÿง‘โ€๐Ÿ’ป Kotlin

Kotlin Playground

Annotations array notation

@[Inject Named("foo")]
lateinit var foo: String

/* is equivalent to */

@Inject @Named("foo")
lateinit var foo: String

If you have multiple annotations with the same target, you can avoid repeating the target by adding brackets after the target and putting all the annotations inside the brackets: ๐Ÿ”—

@set:[Inject VisibleForTesting Named("foo")]
lateinit var foo: String

BitFlags

import kotlin.experimental.*

inline fun Long.hasFlag(flag: Long): Boolean = flag and this == flag
inline fun Long.withFlag(flag: Long): Long = this or flag
inline fun Long.minusFlag(flag: Long): Long = this and flag.inv()

inline fun Int.hasFlag(flag: Int): Boolean = flag and this == flag
inline fun Int.withFlag(flag: Int): Int = this or flag
inline fun Int.minusFlag(flag: Int): Int = this and flag.inv()

inline fun Short.hasFlag(flag: Short): Boolean = flag and this == flag
inline fun Short.withFlag(flag: Short): Short = this or flag
inline fun Short.minusFlag(flag: Short): Short = this and flag.inv()

inline fun Byte.hasFlag(flag: Byte): Boolean = flag and this == flag
inline fun Byte.withFlag(flag: Byte): Byte = this or flag
inline fun Byte.minusFlag(flag: Byte): Byte = this and flag.inv()

Calling internal code

The internal visibility modifier means that the member is visible within the same module. ๐Ÿ”—

project1/src/main/kotlin/Main.kt
internal fun foo(any: Bar) = println(any)
internal class Bar(bar: Bar? = null)
internal val baz = Bar()
project2/src/main/kotlin/Main.kt
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
foo(Bar(baz))

Check if a class is in the classpath

internal inline fun <R> String.ifInClasspath(value: () -> R): R? = if (isInClasspath()) value() else null

internal fun String.isInClasspath(): Boolean = kotlin.runCatching {
    Class.forName(this)
}.fold(
    onSuccess = { true },
    onFailure = { false },
)

Coroutine's CancellationException with runCatching

kotlin.runCatching is not coroutines-aware, and therefore does not automatically rethrow CancellationException.

package kotlinx.coroutines

/**
 * Executes [kotlin.runCatching] but rethrow any [CancellationException].
 * @see kotlin.runCatching
 */
suspend inline fun <R> runSuspendCatching(block: () -> R): Result<R> = runCatching(block).onFailure {
    if (it is CancellationException) throw it
}

/**
 * Executes [kotlin.runCatching] but rethrow any [CancellationException].
 * @see kotlin.runCatching
 */
suspend inline fun <T, R> T.runSuspendCatching(block: T.() -> R): Result<R> = runCatching(block).onFailure {
    if (it is CancellationException) throw it
}

๐Ÿง‘โ€๐Ÿ’ป

DeepRecursiveFunction

Quote

Defines deep recursive function that keeps its stack on the heap, which allows very deep recursive computations that do not use the actual call stack. As a rule of thumb, it should be used if recursion goes deeper than a thousand calls.

class Tree(val left: Tree? = null, val right: Tree? = null)

val tree = generateSequence(Tree(), ::Tree).take(100_000).last()

/* Regular recursive depth will produce a StackOverflowError */
fun Tree?.recursiveDepth(): Int =
    if (this == null) 0 else max(left.recursiveDepth(), right.recursiveDepth()) + 1

println(tree.recursiveDepth()) // StackOverflowError

/* DeepRecursiveFunction using heap stack instead of call stack */
val deepRecursiveDepth = DeepRecursiveFunction<Tree?, Int> {
    if (it == null) 0 else max(callRecursive(it.left), callRecursive(it.right)) + 1
}

println(deepRecursiveDepth(tree)) // ๐Ÿ‘

๐Ÿ”—

Enum with annotation

annotation class DisplayName(val value: String)

enum class Data {
    @DisplayName("a") Foo,
    @DisplayName("b") Bar,
    @DisplayName("c") Baz,
}

inline fun <reified A : Annotation> Enum<*>.annotation(): A? =
    this::class.java.getField(name).getAnnotation(A::class.java)

val displayName = Foo.annotation<DisplayName>()?.value // "a"

Execute commands in subprocess

On Windows (), you'll probably need to prefix the command with cmd /c.

/**
 * ```
 * val status: String = """git status""".exec()
 * ```
 */
fun @receiver:Language("bash") String.exec() = Runtime.getRuntime().exec(this).text()

/**
 * ```
 * val diff: String = """git diff""".execute {
 *     environment() += ("GIT_DIFF_OPTS" to "--unified=10")
 * }.text()
 * ```
 */
fun @receiver:Language("bash") String.execute(
    workingDir: File = Paths.get(".").toFile(),
    redirectOutput: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE,
    redirectError: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE,
    configure: ProcessBuilder.() -> Unit = {},
): Process = ProcessBuilder("""\s""".toRegex().split(this))
    .directory(workingDir)
    .redirectOutput(redirectOutput)
    .redirectError(redirectError)
    .apply(configure)
    .start()

private fun Process.text() = apply { waitFor() }.inputStream.bufferedReader().use { it.readText().trim() }

Expect test failures

The following way of checking a test throws is strongly discouraged because of this single reason: it will succeed wherever the exception comes from, including from the setup code.

@Test(expected = ArithmeticException::class)
fun test() { /* ... */ }

Instead, the better solution is to use:

@Test
fun kotlinTest() {
    kotlin.test.assertFailsWith<ArithmeticException> { 1/0 }
}

@Test()
fun junit() {
    org.junit.jupiter.api.assertThrows<ArithmeticException> { 1/0 }
}

Also, the expected parameter has been removed from JUnit5.

Extensions

Collections

fun <T> Sequence<T>.repeat() = sequence { while (true) yieldAll(this@repeat) }

fun <T> Sequence<T>.repeat(n: Int) = sequence { repeat(n) inner@{ yieldAll(this@repeat) } }

inline fun <T, R> Iterable<T>.foldInPlace(
    initial: R,
    operation: R.(T) -> Unit,
): R = fold(initial) { acc: R, t: T -> acc.apply { operation(t) } }

inline fun <T> Iterable<T>.partitionIndexed(
    predicate: (index: Int, T) -> Boolean,
): Pair<List<T>, List<T>> = withIndex()
    .partition { (index, value) -> predicate(index, value) }
    .map { it.map(IndexedValue<T>::value) }

Maths

@JvmName("intProduct")
fun Iterable<Int>.product(): Long = fold(1L, Long::times)

@JvmName("longProduct")
fun Iterable<Long>.product(): Long = fold(1L, Long::times)

fun <T> Iterable<T>.cartesianSquare(): List<Pair<T, T>> = flatMap { v -> map { v to it } }

/**
 * Euclid's algorithm for finding the greatest common divisor of a and b.
 */
fun gcd(a: Int, b: Int): Int = if (b == 0) a.absoluteValue else gcd(b, a % b)
fun gcd(f: Int, vararg n: Int): Int = n.fold(f, ::gcd)
fun Iterable<Int>.gcd(): Int = reduce(::gcd)

/**
 * Euclid's algorithm for finding the greatest common divisor of a and b.
 */
fun gcd(a: Long, b: Long): Long = if (b == 0L) a.absoluteValue else gcd(b, a % b)
fun gcd(f: Long, vararg n: Long): Long = n.fold(f, ::gcd)
fun Iterable<Long>.gcd(): Long = reduce(::gcd)

/**
 * Find the least common multiple of a and b using the gcd of a and b.
 */
fun lcm(a: Int, b: Int) = (a * b) / gcd(a, b)
fun lcm(f: Int, vararg n: Int): Long = n.map { it.toLong() }.fold(f.toLong(), ::lcm)
fun Iterable<Int>.lcm(): Long = map { it.toLong() }.reduce(::lcm)

/**
 * Find the least common multiple of a and b using the gcd of a and b.
 */
fun lcm(a: Long, b: Long) = (a * b) / gcd(a, b)
fun lcm(f: Long, vararg n: Long): Long = n.fold(f, ::lcm)
fun Iterable<Long>.lcm(): Long = reduce(::lcm)

/**
 * Simple algorithm to find the primes of the given Long.
 */
fun Long.primes(): Sequence<Long> = sequence {
    var n = this@primes
    var j = 2L
    while (j * j <= n) {
        while (n % j == 0L) {
            yield(j)
            n /= j
        }
        j++
    }
    if (n > 1) yield(n)
}

Misc

inline fun <reified T : Any> Any?.cast(): T = this as T

operator fun String.times(n: Int) = repeat(n)

fun <A, B> Pair<A, B>.swap(): Pair<B, A> = Pair(second, first)

inline fun <T, R> Pair<T, T>.map(transform: (T) -> R): Pair<R, R> = transform(first) to transform(second)

Normalize line endings

fun String.normalizeLineEndings(
    replacement: String = System.lineSeparator(),
) = replace("""\r\n?""".toRegex(), replacement)

Permutations & Combinations

fun <T> List<T>.permutations(): List<List<T>> {
    if (isEmpty()) return emptyList()
    if (size == 1) return listOf(this)
    val elementToInsert = first()
    return drop(1).permutations().flatMap { p -> List(size) { p.toMutableList().apply { add(it, elementToInsert) } } }
}

fun <T> List<T>.permutationsSequence(): Sequence<List<T>> {
    if (isEmpty()) return emptySequence()
    if (size == 1) return sequenceOf(this)
    val elementToInsert = first()
    return drop(1).permutationsSequence()
        .flatMap { p -> List(size) { p.toMutableList().apply { add(it, elementToInsert) } } }
}

fun <T> List<T>.pairs(): Sequence<Pair<T, T>> = sequence {
    for (i in 0 until size - 1)
        for (j in i + 1 until size) {
            yield(this@pairs[i] to this@pairs[j])
            yield(this@pairs[j] to this@pairs[i])
        }
}

Filter non-null values in a Map

Kotlin already has a Iterable<T?>.filterNotNull(): List<T> but nothing for Maps. ๐Ÿคท

/**
 * Returns a map containing all key-value pairs with values that are not `null`.
 * The returned map preserves the entry iteration order of the original map.
 *
 * @see filterNotNull
 */
@Suppress("UNCHECKED_CAST")
fun <K, V: Any> Map<K, V?>.filterNotNullValues(): Map<K, V> = filterValues { it != null } as Map<K, V>

Implicit lamda result with run and let

When using the following syntax, both doSomething() and fallback() will be exectued when the return value of the lambda is null.

// AVOID
somethingNullable?.run { doSomething() } ?: fallback()
somethingNullable?.let { it.doSomething() } ?: fallback()
// PREFER
somethingNullable?.also { it.doSomething() } ?: fallback()

Infinitely repeating Sequence

fun <T> Sequence<T>.repeat() = sequence { while (true) yieldAll(this@repeat) }
fun <T> Sequence<T>.repeat(n: Int) = sequence { repeat(n) inner@{ yieldAll(this@repeat) } }

Invoke operator on Companion objects

class Operation {
    companion object {
        operator fun invoke(block: () -> Unit) = block()
    }
}

fun main() = Operation { }

Invoke operator on Coroutines Dispatchers

import kotlinx.coroutines.invoke
import kotlinx.coroutines.Dispatchers.IO

suspend fun main() = IO { /* ... */ }

Kotlin script

#!/usr/bin/env kotlin

@file:Repository("https://repo1.maven.org/maven2")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
@file:CompilerOptions("-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi")

import kotlinx.coroutines.runBlocking


runBlocking { 
    println("Hello, World!")
}

Kotlin script compilation cache

The compilation cache of Kotlin scripts .main.kts are stored using either:

  • KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR environment variable
  • kotlin.main.kts.compiled.scripts.cache.dir system property

Language injection

@org.intellij.lang.annotations.Language("JSON")
val json = """{"key": "value"}"""

println(/*language=JSON*/ """{"key": "value"}""")

LazyMutable

var lazyMutable by lazyMutable { Random.nextInt() }

fun <T> lazyMutable(initializer: () -> T): LazyMutable<T> = LazyMutable(initializer)

class LazyMutable<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {

    private object NotInitialized

    private var prop: Any? = NotInitialized

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
            if (prop == NotInitialized) {
                synchronized(this) {
                    return if (prop == NotInitialized) initializer().also { prop = it } else prop as T
                }
            } else prop as T

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = synchronized(this) {
        prop = value
    }
}

MainCoroutineRule

๐Ÿ”—

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description

/**
 * Sets the main coroutines dispatcher to a [StandardTestDispatcher] for unit testing.
 * A [StandardTestDispatcher] provides control over the execution of coroutines.
 * Alternatively, you can use an [UnconfinedTestDispatcher].
 *
 * Declare it as a JUnit Rule:
 *
 * ```
 * @get:Rule
 * var mainCoroutineRule = MainCoroutineRule()
 * ```
 *
 * Then, use `runTest` to execute your tests.
 */
@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestDispatcher = StandardTestDispatcher()) : TestWatcher() {

    override fun starting(description: Description) = Dispatchers.setMain(dispatcher)

    override fun finished(description: Description) = Dispatchers.resetMain()

}

Map parallelized

suspend fun <T, R> Iterable<T>.mapParallelized(
    permits: Int = Int.MAX_VALUE,
    transform: suspend (T) -> R,
): List<R> = coroutineScope {
    val semaphore = Semaphore(permits)
    map { async { semaphore.withPermit { transform(it) } } }.awaitAll()
}

suspend fun <T, R> Iterable<T>.mapParallelized(
    dispatcher: CoroutineDispatcher = Dispatchers.Default,
    parallelism: Int = Int.MAX_VALUE,
    transform: suspend (T) -> R,
): List<R> = coroutineScope {
    val limited = dispatcher.limitedParallelism(parallelism)
    map { async(limited) { transform(it) } }.awaitAll()
}

๐Ÿ”—

Migrate source folders from Java to Kotlin

find . -depth -type d -path "*/src/*" -name "java" -exec git mv "{}" "{}"/../kotlin \;

MockK extension to wait indefinitely

import io.mockk.*
import kotlinx.coroutines.*

/**
 * Stub block to never return.
 *
 * Used to define what behaviour is going to be mocked.
 * @see [io.mockk.coJustRun]
 */
fun coJustAwait(
    stubBlock: suspend MockKMatcherScope.() -> Unit
) = coEvery(stubBlock) coAnswers {
    awaitCancellation()
}

NoWhenBranchMatchedException

Using the java.io.Serializable interface (or custom android.os.Parcelable code) for sealed classe objects might lead to kotlin.NoWhenBranchMatchedException while using the Kotlin's when expression.
The reason is fairly simple but not obviousโ€ฆ

When an object is serialized and then deserialized, the result won't be the same object instance anymore.
But unfortunately, the Kotlin compiler does not warn us about this.
Which means the following test code will fail without any warning:

sealed class SealedClass : Serializable {
    object SealedObject: SealedClass()
}

@Test
fun test() = SealedObject.serialize().deserialize<SealedObject>().check()

fun SealedClass.check() = when(this) {
    SealedObject -> Unit
    // This will crash here with: kotlin.NoWhenBranchMatchedException
}

Even the Add remaining branches context action will fall into this issue.
What must be done instead is to check for structural equality (==) instead of referential equality (===).

fun SealedClass.check() = when(this) {
    is SealedObject -> Unit
    // This will no longer crash
}

This is also the case when creating mock objects (with MockK or Mockito).

Phantom types to represent IDs

@JvmInline value class Id<@Suppress("unused") out T>(val value: String)

data class User(val id: Id<User>)
data class Address(val id: Id<Address>)

val alice = User(id = Id("alice"))
val bob = User(id = Id("bob"))
val home = Address(id = Id("home"))

assert(alice.id == bob.id)
assert(alice.id == home.id) // Operator '==' cannot be applied to 'Id<User>' and 'Id<Address>'

Or even better with generic value types:

@JvmInline value class Id<@Suppress("unused") out T, out Value>(val value: Value)

data class User(val id: Id<User, Long>)
data class Address(val id: Id<Address, String>)

val alice = User(id = Id(0))
val bob = User(id = Id(1))
val home = Address(id = Id(0))

assert(alice.id == bob.id)
assert(alice.id == home.id) // Operator '==' cannot be applied to 'Id<User, Long>' and 'Id<Address, Long>'

๐Ÿ”—

Property delegate to cancel previous Job

import kotlinx.coroutines.Job
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
 * This delegate takes care of cancelling the previous [Job] when a new one is assigned.
 */
class CancelPreviousJob : ReadWriteProperty<Any?, Job?> {
    private var job: Job? = null
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): Job? = job
    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Job?) {
        job?.cancel()
        job = value
    }
}
var job by CancelPreviousJob()
job = launch { /* ... */ }
/* ... */
job = launch { /* ... */ }

Read resource file

object Resources {
    fun readText(name: String): String = this::class.java.classLoader.getResource(name)?.readText().orEmpty()
}

Recursive flatMap

internal fun <T> T.recursiveFlatMap(
    transform: (T) -> Iterable<T>,
): List<T> = listOf(this) + transform(this).flatMap {
    it.recursiveFlatMap(transform)
}
data class Node(val id: String, val children: List<Node> = emptyList())

val node = Node("1", children = listOf(Node("2", children = listOf(Node("3")))))
val nodes = node.recursiveFlatMap(Node::children)
// [Node(id=1, children=[Node(id=2, children=[Node(id=3)])]), Node(id=2, children=[Node(id=3)]), Node(id=3)]

Regex destructuring

val regex = """(\d+)-(\d+)-(\d+)""".toRegex()

fun String.toDate(input: String): Date? = regex.find(this)
    ?.destructured
    ?.let { (y, m, d) ->
        Date(year = y.toInt(), month = m.toInt(), day = d.toInt())
    }

Regex destructuring capturing groups

operator fun MatchResult.getValue(
    thisRef: Any?,
    property: KProperty<*>
): String = groups[property.name]!!.value

val regex = """(?<year>\d{4})-(?<month>\d{1,2})-(?<day>\d{1,2})""".toRegex()
val result = regex.find("2022-10-12") ?: return

val year: String by result
val month: String by result
val day: String by result

println("$year-$month-$day")

Reified generic in virtual functions

interface Repository<T: Any> {
    fun get(key: String, clazz: KClass<T>) : T
}

inline fun <reified T: Any> Repository<T>.get(key: String): T = get(key, T::class)

fun main(repository: Repository<Int>) {
    val int = repository["key", Int::class]
    // becomes
    val int = repository["key"]
}

Require an instance type

import kotlin.contracts.*

/**
 * Throws an [IllegalArgumentException] if the [value] is not of type [T].
 */
@OptIn(ExperimentalContracts::class)
inline fun <reified T> requireInstanceOf(value: Any?): T {
    contract { returns() implies (value is T) }
    return requireInstanceOf(value) {
        "Required value was ${if (value == null) "null" else value::class.qualifiedName} instead of ${T::class.qualifiedName}"
    }
}

/**
 * Throws an [IllegalArgumentException] with the result of calling [lazyMessage] if the [value] is not of type [T].
 */
@OptIn(ExperimentalContracts::class)
inline fun <reified T> requireInstanceOf(value: Any?, lazyMessage: () -> Any): T {
    contract { returns() implies (value is T) }
    require(value is T, lazyMessage)
    return value
}

Retry coroutine operations

import kotlinx.coroutines.*
import kotlin.time.*

suspend fun <T> retry(
    times: Int,
    delay: Duration,
    block: suspend  () -> T,
): T {
    require(times > 1) { "Expected times > 1, but had $times" }
    repeat(times - 1) {
        kotlin.runCatching { block() }
            .onSuccess { return it }
            .onFailure { delay(delay) }
    }
    return block()
}

Sealed object instances

SimonMarquis/SealedObjectInstances

import kotlin.reflect.KClass

fun <T : Any> KClass<T>.sealedObjectInstances() = recursiveSealedObjectInstances()

private tailrec fun <T : Any> KClass<T>.recursiveSealedObjectInstances(
    sealedSubclasses: List<KClass<out T>> = listOf(this),
    acc: Set<T> = emptySet(),
): Set<T> = when {
    sealedSubclasses.isEmpty() -> acc
    else -> recursiveSealedObjectInstances(
        acc = acc + sealedSubclasses.mapNotNull { it.objectInstance },
        sealedSubclasses = sealedSubclasses.flatMap { it.sealedSubclasses },
    )
}
fun main() = Sealed::class.sealedObjectInstances()
    .joinToString(separator = "\n") { it.javaClass.name }
    .let(::println)

// Sealed$SealedObject
// Sealed$SealedObject$NestedSealedObject
// ExternalSealedObject
// Sealed$SubSealed$SubSealedObject

sealed class Sealed {
    object SealedObject : Sealed() {
        object NestedSealedObject : Sealed()
    }
    sealed class SubSealed : Sealed() {
        object SubSealedObject : SubSealed()
        data class DataSubSealed(val any: Any) : SubSealed()
    }
}
object ExternalSealedObject : Sealed()

๐Ÿ”—

SemVer

data class SemVer(
    val major: Int = 0,
    val minor: Int = 0,
    val patch: Int = 0,
    val label: String? = null
) : Comparable<SemVer> {

    companion object {
        fun parse(version: String): SemVer {
            val pattern = Regex("""(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-(.+))?""")
            val result = pattern.matchEntire(version)
                ?: throw IllegalArgumentException("Invalid version string [$version]")
            return SemVer(
                major = result.groupValues[1].toIntOrNull() ?: 0,
                minor = result.groupValues[2].toIntOrNull() ?: 0,
                patch = result.groupValues[3].toIntOrNull() ?: 0,
                label = result.groupValues[4].let { it.ifEmpty { null } }
            )
        }
    }

    init {
        require(major >= 0) { "Major version must be a positive number" }
        require(minor >= 0) { "Minor version must be a positive number" }
        require(patch >= 0) { "Patch version must be a positive number" }
        label?.let { require(it.matches(Regex(""".+"""))) { "Label is not valid" } }
    }

    override fun toString(): String = buildString {
        append("$major.$minor.$patch")
        label?.let { append("-$it") }
    }

    /**
     * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
     */
    override fun compareTo(other: SemVer): Int {
        if (major != other.major) return major.compareTo(other.major)
        if (minor != other.minor) return minor.compareTo(other.minor)
        if (patch != other.patch) return patch.compareTo(other.patch)
        if (label == other.label) return 0
        if (label.isNullOrEmpty()) return 1 // 1.0.0 > 1.0.0-alpha
        if (other.label.isNullOrEmpty()) return -1  // 1.0.0-alpha < 1.0.0
        return label.compareTo(other.label, ignoreCase = true)
    }

}

Singleton

open class Singleton<out T, in A>(creator: (A) -> T) {

    private var creator: ((A) -> T)? = creator

    @Volatile
    private var instance: T? = null

    fun instance(arg: A): T {
        val i = instance
        if (i != null) return i
        return synchronized(this) {
            val i2 = instance
            if (i2 != null) {
                i2
            } else {
                val created = creator!!(arg)
                instance = created
                creator = null
                created
            }
        }
    }
}

๐Ÿ”—

Synchronize a Map with a new Map of values

/**
 * Synchronize this [MutableMap] with a new [Map] of items, based on their keys.
 * @param onRemoved is called for each removed entry.
 * @param update is called for each entry that must be updated, with the corresponding [T] data.
 * @param onAdded is called for each new entry that must be inserted, and for which you need to provide a corresponding [Value].
 */
fun <Key, Value, T> MutableMap<Key, Value>.sync(
    items: Map<Key, T>,
    onRemoved: (entry: Map.Entry<Key, Value>) -> Unit,
    update: (entry: MutableMap.MutableEntry<Key, Value>, data: T) -> Unit,
    onAdded: (entry: Map.Entry<Key, T>) -> Value,
) = apply {
    val itemsToRemove = filterKeys { it !in items }
    val itemsToUpdate = items.filterKeys { it in this }
    val itemsToAdd = items.filterKeys { it !in this }

    // Remove
    entries.removeAll(itemsToRemove.entries)
    itemsToRemove.forEach { onRemoved(it) }

    // Update
    entries.forEach { update(it, itemsToUpdate[it.key]!!) }

    // Add
    itemsToAdd.forEach { put(it.key, onAdded(it)) }
}

takeWhileInclusive

Similar to fun <T> Sequence<T>.takeWhile(predicate: (T) -> Boolean): Sequence<T> but also includes the next non-matching element as well.

fun <T> Sequence<T>.takeWhileInclusive(predicate: (T) -> Boolean): Sequence<T> {
    var shouldContinue = true
    return takeWhile { shouldContinue.also { _ -> shouldContinue = predicate(it) } }
}

๐Ÿ”— ๐Ÿ”—

Timeout for async operations

suspend fun <T> Deferred<T>.await(
    duration: Duration,
    defaultValue: T,
): T = withTimeout(duration) { await() } ?: defaultValue

Warnings as errors

tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions {
        allWarningsAsErrors = true
    }
}