๐งโ๐ป Kotlin
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. ๐
internal fun foo(any: Bar) = println(any)
internal class Bar(bar: Bar? = null)
internal val baz = Bar()
@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 Map
s. ๐คท
/**
* 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¶
- REPL Worksheet file
*.ws.kts
- Simple but extendable utility scripts
*.main.kts
๐ Keep ๐ Github ๐ Docs
#!/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 variablekotlin.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
}
}