From e05d5352a3cb4386dfb6cd54f0b892950733d65c Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Wed, 22 Nov 2023 18:21:22 +0100 Subject: [PATCH 1/6] Start working on STM-over-Compose --- .../fx/arrow-fx-stm-compose/build.gradle.kts | 94 +++++++++++++++++++ .../fx/arrow-fx-stm-compose/gradle.properties | 4 + .../kotlin/arrow/fx/stm/compose/STM.kt | 2 + build.gradle.kts | 2 + gradle.properties | 5 +- gradle/libs.versions.toml | 7 ++ settings.gradle.kts | 10 ++ 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/gradle.properties create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt diff --git a/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts b/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts new file mode 100644 index 00000000000..282a44c9388 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts @@ -0,0 +1,94 @@ +@file:Suppress("DSL_SCOPE_VIOLATION") + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + +repositories { + google() + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") +} + +plugins { + id(libs.plugins.kotlin.multiplatform.get().pluginId) + // alias(libs.plugins.arrowGradleConfig.kotlin) + alias(libs.plugins.arrowGradleConfig.publish) + alias(libs.plugins.spotless) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.android.library) +} + +apply(from = property("ANIMALSNIFFER_MPP")) + +kotlin { + explicitApi() + + jvm { + jvmToolchain(8) + } + js(IR) { + browser() + nodejs() + } + androidTarget() + // Native: https://kotlinlang.org/docs/native-target-support.html + // -- Tier 1 -- + linuxX64() + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() + // -- Tier 2 -- + // linuxArm64() + watchosSimulatorArm64() + watchosX64() + watchosArm64() + tvosSimulatorArm64() + tvosX64() + tvosArm64() + iosArm64() + // -- Tier 3 -- + mingwX64() + + sourceSets { + commonMain { + dependencies { + api(libs.coroutines.core) + api(compose.runtime) + implementation(libs.kotlin.stdlibCommon) + } + } + + commonTest { + dependencies { + implementation(libs.kotlin.test) + implementation(libs.kotest.assertionsCore) + implementation(libs.kotest.property) + } + } + } +} + +tasks.withType().configureEach { + useJUnitPlatform() +} + +compose { + // override the choice of Compose if we use a Kotlin -dev version + val kotlinVersion = project.rootProject.properties["kotlin_version"] as? String + if (kotlinVersion != null && kotlinVersion.contains("-dev-")) { + kotlinCompilerPlugin.set(dependencies.compiler.forKotlin("1.9.20")) + kotlinCompilerPluginArgs.add("suppressKotlinVersionCompatibilityCheck=$kotlinVersion") + } +} + +android { + namespace = "arrow.fx.stm.compose" + compileSdk = libs.versions.android.compileSdk.get().toInt() +} + +tasks.named("jvmJar").configure { + manifest { + attributes["Automatic-Module-Name"] = "arrow.fx.stm.compose" + } +} diff --git a/arrow-libs/fx/arrow-fx-stm-compose/gradle.properties b/arrow-libs/fx/arrow-fx-stm-compose/gradle.properties new file mode 100644 index 00000000000..ecb9398ca38 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/gradle.properties @@ -0,0 +1,4 @@ +# Maven publishing configuration +pom.name=Arrow Fx STM based on Compose +# Build configuration +kapt.incremental.apt=false \ No newline at end of file diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt new file mode 100644 index 00000000000..5ad2f1d2f45 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt @@ -0,0 +1,2 @@ +package arrow.fx.stm.compose + diff --git a/build.gradle.kts b/build.gradle.kts index c83b401be48..04d0bef23c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,7 @@ allprojects { plugins { base + alias(libs.plugins.android.library) apply false alias(libs.plugins.dokka) alias(libs.plugins.animalSniffer) apply false alias(libs.plugins.kotest.multiplatform) apply false @@ -46,6 +47,7 @@ plugins { alias(libs.plugins.kotlin.binaryCompatibilityValidator) alias(libs.plugins.arrowGradleConfig.nexus) alias(libs.plugins.spotless) apply false + alias(libs.plugins.jetbrainsCompose) apply false } apply(plugin = libs.plugins.kotlinx.knit.get().pluginId) diff --git a/gradle.properties b/gradle.properties index 2f87e20b88c..930db93e55b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ kotlin.mpp.stability.nowarn=true # https://youtrack.jetbrains.com/issue/KT-32476 kotlin.native.ignoreIncorrectDependencies=true kotlin.native.ignoreDisabledTargets=true -kotlin.native.cacheKind.linuxX64=none +# kotlin.native.cacheKind.linuxX64=none # https://youtrack.jetbrains.com/issue/KT-45545#focus=Comments-27-4773544.0-0 kapt.use.worker.api=false kotlin.mpp.applyDefaultHierarchyTemplate=false @@ -37,3 +37,6 @@ ANIMALSNIFFER=../../gradle/animalsniffer.gradle ANIMALSNIFFER_MPP=../../gradle/animalsniffer-mpp.gradle dokkaEnabled=false + + +android.useAndroidX=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ccf8d238cc..33b576e08e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,10 @@ mockWebServer = "4.12.0" retrofit = "2.9.0" retrofitKotlinxSerialization = "1.0.0" spotlessVersion = "6.22.0" +compose = "1.5.4" +composePlugin = "1.5.10" +agp = "8.1.3" +android-compileSdk = "34" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } @@ -53,6 +57,7 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kotlinCompileTesting" } kotlinCompileTestingKsp = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kotlinCompileTesting" } +compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } [plugins] animalSniffer = { id = "ru.vyarus.animalsniffer", version.ref = "animalSniffer" } @@ -71,3 +76,5 @@ kotlinx-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerializationPlugin" } ksp = { id = "com.google.devtools.ksp", version.ref = "kspVersion" } spotless = { id = "com.diffplug.spotless", version.ref = "spotlessVersion" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "composePlugin" } +android-library = { id = "com.android.library", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 3957a87e1a0..dc3998005ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,8 @@ pluginManagement { mavenCentral() mavenLocal() kotlin_repo_url?.also { maven(it) } + google() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } } @@ -20,6 +22,7 @@ plugins { dependencyResolutionManagement { @Suppress("LocalVariableName") val kotlin_repo_url: String? by settings @Suppress("LocalVariableName") val kotlin_version: String? by settings + @Suppress("LocalVariableName") val compose_version: String? by settings repositories { mavenCentral() @@ -33,6 +36,10 @@ dependencyResolutionManagement { println("Overriding Kotlin version with $kotlin_version") version("kotlin", kotlin_version!!) } + if (!compose_version.isNullOrBlank()) { + println("Overriding Compose version with $compose_version") + version("composePlugin", compose_version!!) + } } } } @@ -63,6 +70,9 @@ project(":arrow-fx-coroutines").projectDir = file("arrow-libs/fx/arrow-fx-corout include("arrow-fx-stm") project(":arrow-fx-stm").projectDir = file("arrow-libs/fx/arrow-fx-stm") +include("arrow-fx-stm-compose") +project(":arrow-fx-stm-compose").projectDir = file("arrow-libs/fx/arrow-fx-stm-compose") + include("arrow-resilience") project(":arrow-resilience").projectDir = file("arrow-libs/resilience/arrow-resilience") From 42e98f9c7ece629accae2a8a831a07449007ba7e Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Thu, 23 Nov 2023 10:47:55 +0100 Subject: [PATCH 2/6] First attempt --- .../fx/arrow-fx-stm-compose/build.gradle.kts | 5 +- .../kotlin/arrow/fx/stm/compose/Atomically.kt | 133 ++++++++++ .../kotlin/arrow/fx/stm/compose/STM.kt | 22 ++ .../kotlin/arrow/fx/stm/compose/Structures.kt | 14 + .../kotlin/arrow/fx/stm/compose/STMTest.kt | 251 ++++++++++++++++++ 5 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Atomically.kt create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt diff --git a/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts b/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts index 282a44c9388..77d543df8a0 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts +++ b/arrow-libs/fx/arrow-fx-stm-compose/build.gradle.kts @@ -53,8 +53,9 @@ kotlin { sourceSets { commonMain { dependencies { - api(libs.coroutines.core) api(compose.runtime) + implementation(projects.arrowCore) + implementation(libs.coroutines.core) implementation(libs.kotlin.stdlibCommon) } } @@ -62,8 +63,10 @@ kotlin { commonTest { dependencies { implementation(libs.kotlin.test) + implementation(libs.coroutines.test) implementation(libs.kotest.assertionsCore) implementation(libs.kotest.property) + implementation(projects.arrowFxCoroutines) } } } diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Atomically.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Atomically.kt new file mode 100644 index 00000000000..05d0894afa1 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Atomically.kt @@ -0,0 +1,133 @@ +package arrow.fx.stm.compose + +import androidx.compose.runtime.snapshots.MutableSnapshot +import androidx.compose.runtime.snapshots.Snapshot +import androidx.compose.runtime.snapshots.SnapshotApplyResult +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either +import kotlinx.coroutines.sync.Mutex +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +public tailrec suspend fun atomically(f: STM.() -> A): A { + val readVariables = mutableSetOf() + val snapshot = Snapshot.takeMutableSnapshot( + readObserver = readVariables::add + ) + fourCases( + runSnapshotEither(snapshot, f), + snapshot, + onException = { e -> throw e }, + onRetry = { + snapshot.dispose() + if (readVariables.isEmpty()) throw BlockedIndefinitely() + // set up a listener for changes + val mutex = Mutex(locked = true) + val observer = Snapshot.registerApplyObserver { changes, _ -> + if (readVariables.any { it in changes }) { + mutex.unlock() + } + } + // wait until one of the read variables changes + mutex.lock() + observer.dispose() + return@atomically atomically(f) + }, + onApplySuccess = { return@atomically it }, + onApplyFailure = { return@atomically atomically(f)} + ) + throw IllegalStateException("atomically: you should never reach this") +} + +private fun runSnapshotEither( + snapshot: MutableSnapshot, + f: STM.() -> A +): Either = snapshot.enter { + either { + catch( + block = { f(ComposeRaiseSTM(snapshot, this)) }, + catch = { raise(STMOtherThrowable(it)) } + ) + } +} + +@OptIn(ExperimentalContracts::class) +private inline fun fourCases( + result: Either, + snapshot: MutableSnapshot, + onException: (Throwable) -> Unit, + onRetry: () -> Unit, + onApplySuccess: (A) -> Unit, + onApplyFailure: (A) -> Unit +) { + contract { + callsInPlace(onException, InvocationKind.AT_MOST_ONCE) + callsInPlace(onRetry, InvocationKind.AT_MOST_ONCE) + callsInPlace(onApplySuccess, InvocationKind.AT_MOST_ONCE) + callsInPlace(onApplyFailure, InvocationKind.AT_MOST_ONCE) + } + when (result) { + is Either.Left -> when (val r = result.value) { + is STMOtherThrowable -> { + snapshot.dispose() + onException(r.error) + } + + is STMRetry -> { + snapshot.dispose() + onRetry() + } + } + + is Either.Right -> { + when (snapshot.apply()) { + SnapshotApplyResult.Success -> + onApplySuccess(result.value) + + is SnapshotApplyResult.Failure -> { + snapshot.dispose() + onApplyFailure(result.value) + } + } + } + } +} + +private sealed interface STMProblem +private data object STMRetry: STMProblem +private data class STMOtherThrowable(val error: Throwable): STMProblem + +private class ComposeRaiseSTM( + val snapshot: MutableSnapshot, + val raise: Raise +): STM, Raise by raise { + override fun retry(): Nothing = raise.raise(STMRetry) + override fun (STM.() -> A).orElse(other: STM.() -> A): A { + val nestedSnapshot = snapshot.takeNestedMutableSnapshot() + fourCases( + runSnapshotEither(nestedSnapshot, this), + nestedSnapshot, + onException = { e -> raise.raise(STMOtherThrowable(e)) }, + onRetry = { return@orElse other(this@ComposeRaiseSTM) }, + onApplySuccess = { return@orElse it }, + onApplyFailure = { throw IllegalStateException("nested orElse failed") } + ) + throw IllegalStateException("orElse: you should never reach this") + } + + override fun catch(f: STM.() -> A, onError: STM.(Throwable) -> A): A { + val nestedSnapshot = snapshot.takeNestedMutableSnapshot() + fourCases( + runSnapshotEither(nestedSnapshot, f), + nestedSnapshot, + onException = { e -> return@catch onError(this@ComposeRaiseSTM, e) }, + onRetry = { raise.raise(STMRetry) }, + onApplySuccess = { return@catch it }, + onApplyFailure = { throw IllegalStateException("nested orElse failed") } + ) + throw IllegalStateException("catch: you should never reach this") + } +} diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt index 5ad2f1d2f45..86c31665c51 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt @@ -1,2 +1,24 @@ package arrow.fx.stm.compose +public inline fun stm(noinline f: STM.() -> A): STM.() -> A = f + +public interface STM { + public fun retry(): Nothing + public infix fun (STM.() -> A).orElse(other: STM.() -> A): A + public fun catch(f: STM.() -> A, onError: STM.(Throwable) -> A): A + + public fun check(condition: Boolean) { + if (!condition) retry() + } + + public fun TVar.read(): A = variable.value + public fun TVar.write(value: A) { + variable.value = value + } + public fun TVar.modify(f: (A) -> A) { + variable.value = f(variable.value) + } +} + +public class BlockedIndefinitely : Throwable("Transaction blocked indefinitely") + diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt new file mode 100644 index 00000000000..7f92ac6f048 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt @@ -0,0 +1,14 @@ +package arrow.fx.stm.compose + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import kotlin.jvm.JvmInline + +@JvmInline +public value class TVar(internal val variable: MutableState) { + public fun unsafeRead(): A = variable.value + + public companion object { + public fun new(value: A): TVar = TVar(mutableStateOf(value)) + } +} diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt new file mode 100644 index 00000000000..e150882f4d2 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt @@ -0,0 +1,251 @@ +package arrow.fx.stm.compose + +import arrow.fx.coroutines.parZip +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.boolean +import io.kotest.property.arbitrary.int +import io.kotest.property.checkAll +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlin.test.Test +import kotlin.time.ExperimentalTime +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.time.Duration.Companion.microseconds +import kotlin.time.Duration.Companion.milliseconds + +@ExperimentalTime +class STMTest { + + @Test fun noEffects() = runTest { + atomically { 10 } shouldBeExactly 10 + } + + @Test fun readingFromVars() = runTest { + checkAll(Arb.int()) { i: Int -> + val tv = TVar.new(i) + atomically { + tv.read() + } shouldBeExactly i + tv.unsafeRead() shouldBeExactly i + } + } + + @Test fun readingAndWriting() = runTest { + checkAll(Arb.int(), Arb.int()) { i: Int, j: Int -> + val tv = TVar.new(i) + atomically { tv.write(j) } + tv.unsafeRead() shouldBeExactly j + } + } + + @Test fun readAfterAWriteShouldHaveTheUpdatedValue() = runTest { + checkAll(Arb.int(), Arb.int()) { i: Int, j: Int -> + val tv = TVar.new(i) + atomically { tv.write(j); tv.read() } shouldBeExactly j + tv.unsafeRead() shouldBeExactly j + } + } + + @Test fun readingMultipleVariables() = runTest { + checkAll(Arb.int(), Arb.int(), Arb.int()) { i: Int, j: Int, k: Int -> + val v1 = TVar.new(i) + val v2 = TVar.new(j) + val v3 = TVar.new(k) + atomically { v1.read() + v2.read() + v3.read() } shouldBeExactly i + j + k + v1.unsafeRead() shouldBeExactly i + v2.unsafeRead() shouldBeExactly j + v3.unsafeRead() shouldBeExactly k + } + } + + @Test fun readingAndWritingMultipleVariables() = runTest { + checkAll(Arb.int(), Arb.int(), Arb.int()) { i: Int, j: Int, k: Int -> + val v1 = TVar.new(i) + val v2 = TVar.new(j) + val v3 = TVar.new(k) + val sum = TVar.new(0) + atomically { + val s = v1.read() + v2.read() + v3.read() + sum.write(s) + } + v1.unsafeRead() shouldBeExactly i + v2.unsafeRead() shouldBeExactly j + v3.unsafeRead() shouldBeExactly k + sum.unsafeRead() shouldBeExactly i + j + k + } + } + + @Test fun retryWithoutPriorReadsThrowsAnException() = runTest { + shouldThrow { atomically { retry() } } + } + + @Test fun retryShouldSuspendForeverIfNoReadVariableChanges() = runTest { + withTimeoutOrNull(500.milliseconds) { + val tv = TVar.new(0) + atomically { + if (tv.read() == 0) retry() + else 200 + } + } shouldBe null + } + + @Test fun aSuspendedTransactionWillResumeIfAVariableChanges() = runTest { + val tv = TVar.new(0) + val f = async { + delay(500.milliseconds) + atomically { tv.modify { it + 1 } } + } + atomically { + when (val i = tv.read()) { + 0 -> retry() + else -> i + } + } shouldBeExactly 1 + f.join() + } + + @Test fun aSuspendedTransactionWillResumeIfAnyVariableChanges() = runTest { + val v1 = TVar.new(0) + val v2 = TVar.new(0) + val v3 = TVar.new(0) + val f = async { + delay(500.milliseconds) + atomically { v1.modify { it + 1 } } + delay(500.milliseconds) + atomically { v2.modify { it + 1 } } + delay(500.milliseconds) + atomically { v3.modify { it + 1 } } + } + atomically { + val i = v1.read() + v2.read() + v3.read() + check(i >= 3) + i + } shouldBeExactly 3 + f.join() + } + + @Test fun retryOrElseRetryOrElseT1T1() = runTest { + atomically { + stm { retry() } orElse { 10 } + } shouldBeExactly 10 + } + + @Test fun retryOrElseT1OrElseRetryT1() = runTest { + atomically { + stm { 10 } orElse { retry() } + } shouldBeExactly 10 + } + + @Test fun retryOrElseAssociativity() = runTest { + checkAll(Arb.boolean(), Arb.boolean(), Arb.boolean()) { b1: Boolean, b2: Boolean, b3: Boolean -> + if ((b1 || b2 || b3).not()) { + shouldThrow { + atomically { + stm { stm { check(b1) } orElse { check(b2) } } orElse { check(b3) } + } + } shouldBe shouldThrow { + atomically { + stm { check(b1) } orElse { stm { check(b2) } orElse { check(b3) } } + } + } + } else { + atomically { + stm { stm { check(b1) } orElse { check(b2) } } orElse { check(b3) } + } shouldBe atomically { + stm { check(b1) } orElse { stm { check(b2) } orElse { check(b3) } } + } + } + } + } + + @Test fun suspendedTransactionsAreResumedForVariablesAccessedInOrElse() = runTest { + val tv = TVar.new(0) + val f = async { + delay(10.microseconds) + atomically { tv.modify { it + 1 } } + } + atomically { + stm { + when (val i = tv.read()) { + 0 -> retry() + else -> i + } + } orElse { retry() } + } shouldBeExactly 1 + f.join() + } + + /* + @Test fun onASingleVariableConcurrentTransactionsShouldBeLinear() = runTest { + val tv = TVar.new(0) + val res = TQueue.new() + + (0..100).map { + async { + atomically { + val r = tv.read().also { tv.write(it + 1) } + res.write(r) + } + } + }.joinAll() + + atomically { res.flush() } shouldBe (0..100).toList() + } + + */ + + @Test fun atomicallyRethrowsExceptions() = runTest { + shouldThrow { atomically { throw IllegalArgumentException("Test") } } + } + + @Test fun throwingAnExceptionsShouldVoidAllStateChanges() = runTest { + val tv = TVar.new(10) + shouldThrow { + atomically { tv.write(30); throw IllegalArgumentException("test") } + } + tv.unsafeRead() shouldBeExactly 10 + } + + @Test fun catchShouldWorkAsExcepted() = runTest { + val tv = TVar.new(10) + val ex = IllegalArgumentException("test") + atomically { + catch({ + tv.write(30) + throw ex + }) { e -> + e shouldBe ex + } + } + tv.unsafeRead() shouldBeExactly 10 + } + + @Test fun concurrentExample1() = runTest { + val acc1 = TVar.new(100) + val acc2 = TVar.new(200) + parZip( + { + // transfer acc1 to acc2 + val amount = 50 + atomically { + val acc1Balance = acc1.read() + check(acc1Balance - amount >= 0) + acc1.write(acc1Balance - amount) + acc2.modify { it + 50 } + } + }, + { + atomically { acc1.modify { it - 60 } } + delay(20.milliseconds) + atomically { acc1.modify { it + 60 } } + }, + { _, _ -> Unit } + ) + acc1.unsafeRead() shouldBeExactly 50 + acc2.unsafeRead() shouldBeExactly 250 + } +} From daed4c9aa22fd854c090cd39a474ff81a972f135 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Thu, 23 Nov 2023 12:32:17 +0100 Subject: [PATCH 3/6] More structures + tests --- .../kotlin/arrow/fx/stm/compose/STM.kt | 47 +++++- .../kotlin/arrow/fx/stm/compose/Structures.kt | 32 ++++ .../kotlin/arrow/fx/stm/compose/TMapTest.kt | 64 ++++++++ .../kotlin/arrow/fx/stm/compose/TQueueTest.kt | 139 ++++++++++++++++++ 4 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TQueueTest.kt diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt index 86c31665c51..59eba11f932 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt @@ -12,11 +12,50 @@ public interface STM { } public fun TVar.read(): A = variable.value - public fun TVar.write(value: A) { - variable.value = value + public fun TVar.write(value: A) { variable.value = value } + public fun TVar.modify(f: (A) -> A) { variable.value = f(variable.value) } + + public operator fun TMap.get(k: K): V? = map[k] + public fun TMap.lookup(k: K): V? = this[k] + + public operator fun TMap.set(k: K, v: V) { map[k] = v } + public operator fun TMap.plusAssign(kv: Pair) { this[kv.first] = kv.second } + public fun TMap.insert(k: K, v: V) { this[k] = v } + public fun TMap.remove(k: K) { map.remove(k) } + public operator fun TMap.contains(k: K): Boolean = k in map + public fun TMap.member(k: K): Boolean = k in this + public fun TMap.update(k: K, f: (V) -> V) { + when (val v = map[k]) { + null -> {} + else -> map[k] = f(v) + } } - public fun TVar.modify(f: (A) -> A) { - variable.value = f(variable.value) + + public operator fun TSet.plusAssign(a: A) { map[a] = true } + public fun TSet.insert(a: A) { this += a } + public fun TSet.remove(a: A) { map.remove(a) } + public operator fun TSet.contains(a: A): Boolean = a in map + public fun TSet.member(a: A): Boolean = a in this + + public fun TQueue.isEmpty(): Boolean = list.isEmpty() + public fun TQueue.isNotEmpty(): Boolean = !isEmpty() + public fun TQueue.size(): Int = list.size + + public fun TQueue.peek(): A = if (list.isEmpty()) retry() else list.first() + public fun TQueue.tryPeek(): A? = list.firstOrNull() + public fun TQueue.read(): A = if (list.isEmpty()) retry() else list.removeFirst() + public fun TQueue.tryRead(): A? = if (list.isEmpty()) null else list.removeFirst() + + public operator fun TQueue.plusAssign(a: A) { list.add(a) } + public fun TQueue.write(a: A) { this += a } + public fun TQueue.writeFront(a: A) { list.add(0, a) } + + public fun TQueue.removeAll(predicate: (A) -> Boolean) { list.removeAll { !predicate(it) } } + + public fun TQueue.flush(): List { + val current = mutableListOf().also { it.addAll(list) } + list.clear() + return current } } diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt index 7f92ac6f048..9718f8863fb 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt @@ -1,7 +1,11 @@ package arrow.fx.stm.compose import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.runtime.snapshots.SnapshotStateMap import kotlin.jvm.JvmInline @JvmInline @@ -12,3 +16,31 @@ public value class TVar(internal val variable: MutableState) { public fun new(value: A): TVar = TVar(mutableStateOf(value)) } } + +@JvmInline +public value class TMap(internal val map: SnapshotStateMap) { + public companion object { + public fun new(): TMap = TMap(mutableStateMapOf()) + } +} + +@JvmInline +public value class TSet(internal val map: SnapshotStateMap) { + public companion object { + public fun new(): TSet = TSet(mutableStateMapOf()) + } +} + +@JvmInline +public value class TList(internal val list: SnapshotStateList) { + public companion object { + public fun new(): TList = TList(mutableStateListOf()) + } +} + +@JvmInline +public value class TQueue(internal val list: SnapshotStateList) { + public companion object { + public fun new(): TQueue = TQueue(mutableStateListOf()) + } +} diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt new file mode 100644 index 00000000000..881f9f5707b --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt @@ -0,0 +1,64 @@ +package arrow.fx.stm.compose + +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.map +import io.kotest.property.checkAll +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class TMapTest { + + @Test fun insertValues() = runTest { + checkAll(Arb.int(), Arb.int()) { k, v -> + val map = TMap.new() + atomically { map.insert(k, v) } + atomically { map.lookup(k) } shouldBe v + } + } + + @Test fun insertMultipleValues() = runTest { + checkAll(Arb.map(Arb.int(), Arb.int())) { pairs -> + val map = TMap.new() + atomically { + for ((k, v) in pairs) map.insert(k, v) + } + atomically { + for ((k, v) in pairs) map.lookup(k) shouldBe v + } + } + } + + @Test fun insertMultipleCollidingValues() = runTest { + checkAll(Arb.map(Arb.int(), Arb.int())) { pairs -> + val map = TMap.new() // hash function that always returns 0 + atomically { + for ((k, v) in pairs) map.insert(k, v) + } + atomically { + for ((k, v) in pairs) map.lookup(k) shouldBe v + } + } + } + + @Test fun insertAndRemove() = runTest { + checkAll(Arb.int(), Arb.int()) { k, v -> + val map = TMap.new() + atomically { map.insert(k, v) } + atomically { map.lookup(k) } shouldBe v + atomically { map.remove(k) } + atomically { map.lookup(k) } shouldBe null + } + } + + @Test fun update() = runTest { + checkAll(Arb.int(), Arb.int(), Arb.int()) { k, v, g -> + val map = TMap.new() + atomically { map.insert(k, v) } + atomically { map.lookup(k) } shouldBe v + atomically { map.update(k) { v + g } } + atomically { map.lookup(k) } shouldBe v + g + } + } +} diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TQueueTest.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TQueueTest.kt new file mode 100644 index 00000000000..8f28ff90b0d --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TQueueTest.kt @@ -0,0 +1,139 @@ +package arrow.fx.stm.compose + +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.int +import io.kotest.property.checkAll +import kotlinx.coroutines.test.runTest +import kotlin.random.Random +import kotlin.test.Test + +class TQueueTest { + + @Test fun writingToAQueueAddsAnElement() = runTest { + val tq = TQueue.new() + atomically { tq.write(10) } + atomically { tq.flush() } shouldBe listOf(10) + } + + @Test fun readingFromAQueueShouldRetryIfTheQueueIsEmpty() = runTest { + val tq = TQueue.new() + atomically { + stm { tq.read().let { true } } orElse { false } + } shouldBe false + } + + @Test fun readingFromAQueueShouldRemoveThatValue() = runTest { + val tq = TQueue.new() + atomically { tq.write(10); tq.write(20) } + atomically { tq.read() } shouldBe 10 + atomically { tq.flush() } shouldBe listOf(20) + } + + @Test fun tryReadBehavesLikeReadIfThereAreValuesToRead() = runTest { + val tq = TQueue.new() + atomically { tq.write(10) } + atomically { tq.tryRead() } shouldBe 10 + atomically { tq.flush() } shouldBe emptyList() + } + + @Test fun tryReadReturnsNullIfTheQueueIsEmpty() = runTest { + val tq = TQueue.new() + atomically { tq.tryRead() } shouldBe null + } + + @Test fun flushEmptiesTheEntireQueueAndReturnsIt() = runTest { + val tq = TQueue.new() + atomically { tq.write(20); tq.write(30); tq.write(40) } + atomically { tq.flush() } shouldBe listOf(20, 30, 40) + atomically { tq.flush() } shouldBe emptyList() + } + + @Test fun readingFlushingShouldWorkAfterMixedReadsWrites() = runTest { + val tq = TQueue.new() + atomically { tq.write(20); tq.write(30); tq.peek(); tq.write(40) } + atomically { tq.read() } shouldBe 20 + atomically { tq.flush() } shouldBe listOf(30, 40) + + atomically { tq.write(20); tq.write(30); tq.peek(); tq.write(40) } + atomically { tq.flush() } shouldBe listOf(20, 30, 40) + atomically { tq.flush() } shouldBe emptyList() + } + + @Test fun peekShouldLeaveTheQueueUnchanged() = runTest { + val tq = TQueue.new() + atomically { tq.write(20); tq.write(30); tq.write(40) } + atomically { tq.peek() } shouldBeExactly 20 + atomically { tq.flush() } shouldBe listOf(20, 30, 40) + } + + @Test fun peekShouldRetryIfTheQueueIsEmpty() = runTest { + val tq = TQueue.new() + atomically { + stm { tq.peek().let { true } } orElse { false } + } shouldBe false + } + + @Test fun tryPeekShouldBehaveLikePeekIfThereAreElements() = runTest { + val tq = TQueue.new() + atomically { tq.write(20); tq.write(30); tq.write(40) } + atomically { tq.peek() } shouldBeExactly + atomically { tq.tryPeek()!! } + atomically { tq.flush() } shouldBe listOf(20, 30, 40) + } + + @Test fun tryPeekShouldReturnNullIfTheQueueIsEmpty() = runTest { + val tq = TQueue.new() + atomically { tq.tryPeek() } shouldBe null + } + + @Test fun isEmptyAndIsNotEmptyShouldWorkCorrectly() = runTest { + val tq = TQueue.new() + atomically { tq.isEmpty() } shouldBe true + atomically { tq.isNotEmpty() } shouldBe false + atomically { tq.write(20) } + atomically { tq.isEmpty() } shouldBe false + atomically { tq.isNotEmpty() } shouldBe true + atomically { tq.peek(); tq.write(30) } + atomically { tq.isEmpty() } shouldBe false + atomically { tq.isNotEmpty() } shouldBe true + } + + @Test fun sizeShouldReturnTheCorrectAmount() = runTest { + checkAll(Arb.int(0..50)) { i -> + val tq = TQueue.new() + atomically { + for (j in 0..i) { + // read to swap read and write lists randomly + if (Random.nextFloat() > 0.9) tq.tryPeek() + tq.write(j) + } + } + atomically { tq.size() } shouldBeExactly i + 1 + } + } + + @Test fun writeFrontShouldWorkCorrectly() = runTest { + val tq = TQueue.new() + atomically { tq.writeFront(203) } + atomically { tq.peek() } shouldBeExactly 203 + atomically { tq.writeFront(50) } + atomically { tq.peek() } shouldBeExactly 50 + atomically { tq.flush() } shouldBe listOf(50, 203) + } + + @Test fun removeAllShouldWork() = runTest { + val tq = TQueue.new() + atomically { tq.removeAll { true } } + atomically { tq.flush() } shouldBe emptyList() + + atomically { + for (i in 0..100) { + tq.write(i) + } + tq.removeAll { it.rem(2) == 0 } + tq.flush() + } shouldBe (0..100).filter { it.rem(2) == 0 } + } +} From bef48fef3cca122ca13e824cf50eb83fbf7b0da8 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Sun, 26 Nov 2023 09:08:12 +0100 Subject: [PATCH 4/6] Expose MutableState directly --- .../kotlin/arrow/fx/stm/compose/STM.kt | 44 ---------- .../kotlin/arrow/fx/stm/compose/Structures.kt | 85 +++++++++++++++---- .../kotlin/arrow/fx/stm/compose/STMTest.kt | 2 +- .../kotlin/arrow/fx/stm/compose/TMapTest.kt | 14 +-- 4 files changed, 77 insertions(+), 68 deletions(-) diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt index 59eba11f932..f8a357600e9 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/STM.kt @@ -11,52 +11,8 @@ public interface STM { if (!condition) retry() } - public fun TVar.read(): A = variable.value - public fun TVar.write(value: A) { variable.value = value } - public fun TVar.modify(f: (A) -> A) { variable.value = f(variable.value) } - - public operator fun TMap.get(k: K): V? = map[k] - public fun TMap.lookup(k: K): V? = this[k] - - public operator fun TMap.set(k: K, v: V) { map[k] = v } - public operator fun TMap.plusAssign(kv: Pair) { this[kv.first] = kv.second } - public fun TMap.insert(k: K, v: V) { this[k] = v } - public fun TMap.remove(k: K) { map.remove(k) } - public operator fun TMap.contains(k: K): Boolean = k in map - public fun TMap.member(k: K): Boolean = k in this - public fun TMap.update(k: K, f: (V) -> V) { - when (val v = map[k]) { - null -> {} - else -> map[k] = f(v) - } - } - - public operator fun TSet.plusAssign(a: A) { map[a] = true } - public fun TSet.insert(a: A) { this += a } - public fun TSet.remove(a: A) { map.remove(a) } - public operator fun TSet.contains(a: A): Boolean = a in map - public fun TSet.member(a: A): Boolean = a in this - - public fun TQueue.isEmpty(): Boolean = list.isEmpty() - public fun TQueue.isNotEmpty(): Boolean = !isEmpty() - public fun TQueue.size(): Int = list.size - public fun TQueue.peek(): A = if (list.isEmpty()) retry() else list.first() - public fun TQueue.tryPeek(): A? = list.firstOrNull() public fun TQueue.read(): A = if (list.isEmpty()) retry() else list.removeFirst() - public fun TQueue.tryRead(): A? = if (list.isEmpty()) null else list.removeFirst() - - public operator fun TQueue.plusAssign(a: A) { list.add(a) } - public fun TQueue.write(a: A) { this += a } - public fun TQueue.writeFront(a: A) { list.add(0, a) } - - public fun TQueue.removeAll(predicate: (A) -> Boolean) { list.removeAll { !predicate(it) } } - - public fun TQueue.flush(): List { - val current = mutableListOf().also { it.addAll(list) } - list.clear() - return current - } } public class BlockedIndefinitely : Throwable("Transaction blocked indefinitely") diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt index 9718f8863fb..15584b40c80 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonMain/kotlin/arrow/fx/stm/compose/Structures.kt @@ -8,34 +8,68 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateMap import kotlin.jvm.JvmInline -@JvmInline -public value class TVar(internal val variable: MutableState) { - public fun unsafeRead(): A = variable.value +public object TVar { + public fun new(value: A): MutableState = mutableStateOf(value) +} - public companion object { - public fun new(value: A): TVar = TVar(mutableStateOf(value)) - } +public fun MutableState.read(): A = value +public fun MutableState.unsafeRead(): A = value +public fun MutableState.write(newValue: A) { value = newValue } +public fun MutableState.modify(transform: (A) -> A) { value = transform(value) } + +public object TMap { + public fun new(): SnapshotStateMap = mutableStateMapOf() } -@JvmInline -public value class TMap(internal val map: SnapshotStateMap) { - public companion object { - public fun new(): TMap = TMap(mutableStateMapOf()) +public fun SnapshotStateMap.insert(key: K, value: V) { this[key] = value } +public fun SnapshotStateMap.update(key: K, transform: (V) -> V): Unit = + when (val v = this[key]) { + null -> { } + else -> { this[key] = transform(v) } } + +public object TList { + public fun new(): SnapshotStateList = mutableStateListOf() } @JvmInline -public value class TSet(internal val map: SnapshotStateMap) { +public value class TSet(internal val map: SnapshotStateMap): MutableSet { public companion object { public fun new(): TSet = TSet(mutableStateMapOf()) } -} -@JvmInline -public value class TList(internal val list: SnapshotStateList) { - public companion object { - public fun new(): TList = TList(mutableStateListOf()) + override fun clear() { map.clear() } + override fun add(element: A): Boolean = map.put(element, true) ?: false + override fun addAll(elements: Collection): Boolean { + var added = false + for (element in elements) { + added = added || add(element) + } + return added } + + override fun remove(element: A): Boolean = + map.remove(element) ?: false + override fun retainAll(elements: Collection): Boolean = + remove { it !in elements } + override fun removeAll(elements: Collection): Boolean = + remove { it in elements } + private fun remove(predicate: (A) -> Boolean): Boolean { + var modified = false + for (k in map.keys.filter(predicate)) { + modified = true + map.remove(k) + } + return modified + } + + override val size: Int get() = map.size + override fun isEmpty(): Boolean = map.isEmpty() + + override fun contains(element: A): Boolean = element in map + override fun containsAll(elements: Collection): Boolean = elements.all { it in map } + + override fun iterator(): MutableIterator = map.keys.iterator() } @JvmInline @@ -43,4 +77,23 @@ public value class TQueue(internal val list: SnapshotStateList) { public companion object { public fun new(): TQueue = TQueue(mutableStateListOf()) } + + public fun isEmpty(): Boolean = list.isEmpty() + public fun isNotEmpty(): Boolean = !isEmpty() + public fun size(): Int = list.size + + public fun tryPeek(): A? = list.firstOrNull() + public fun tryRead(): A? = if (list.isEmpty()) null else list.removeFirst() + + public operator fun plusAssign(a: A) { list.add(a) } + public fun write(a: A) { this += a } + public fun writeFront(a: A) { list.add(0, a) } + + public fun removeAll(predicate: (A) -> Boolean) { list.removeAll { !predicate(it) } } + + public fun flush(): List { + val current = mutableListOf().also { it.addAll(list) } + list.clear() + return current + } } diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt index e150882f4d2..f67ddb4d5c3 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/STMTest.kt @@ -243,7 +243,7 @@ class STMTest { delay(20.milliseconds) atomically { acc1.modify { it + 60 } } }, - { _, _ -> Unit } + { _, _ -> } ) acc1.unsafeRead() shouldBeExactly 50 acc2.unsafeRead() shouldBeExactly 250 diff --git a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt index 881f9f5707b..cf32a213b62 100644 --- a/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt +++ b/arrow-libs/fx/arrow-fx-stm-compose/src/commonTest/kotlin/arrow/fx/stm/compose/TMapTest.kt @@ -14,7 +14,7 @@ class TMapTest { checkAll(Arb.int(), Arb.int()) { k, v -> val map = TMap.new() atomically { map.insert(k, v) } - atomically { map.lookup(k) } shouldBe v + atomically { map[k] } shouldBe v } } @@ -25,7 +25,7 @@ class TMapTest { for ((k, v) in pairs) map.insert(k, v) } atomically { - for ((k, v) in pairs) map.lookup(k) shouldBe v + for ((k, v) in pairs) map[k] shouldBe v } } } @@ -37,7 +37,7 @@ class TMapTest { for ((k, v) in pairs) map.insert(k, v) } atomically { - for ((k, v) in pairs) map.lookup(k) shouldBe v + for ((k, v) in pairs) map[k] shouldBe v } } } @@ -46,9 +46,9 @@ class TMapTest { checkAll(Arb.int(), Arb.int()) { k, v -> val map = TMap.new() atomically { map.insert(k, v) } - atomically { map.lookup(k) } shouldBe v + atomically { map[k] } shouldBe v atomically { map.remove(k) } - atomically { map.lookup(k) } shouldBe null + atomically { map[k] } shouldBe null } } @@ -56,9 +56,9 @@ class TMapTest { checkAll(Arb.int(), Arb.int(), Arb.int()) { k, v, g -> val map = TMap.new() atomically { map.insert(k, v) } - atomically { map.lookup(k) } shouldBe v + atomically { map[k] } shouldBe v atomically { map.update(k) { v + g } } - atomically { map.lookup(k) } shouldBe v + g + atomically { map[k] } shouldBe v + g } } } From 56d2b97a74bb681b9285d9afb18f2f7b10fc6960 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Mon, 11 Dec 2023 15:24:37 +0100 Subject: [PATCH 5/6] Update libs.versions.toml --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b7e94ec2286..a9604c79065 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,8 +23,8 @@ retrofit = "2.9.0" retrofitKotlinxSerialization = "1.0.0" spotlessVersion = "6.23.3" compose = "1.5.4" -composePlugin = "1.5.10" -agp = "8.1.3" +composePlugin = "1.5.11" +agp = "8.2.0" android-compileSdk = "34" [libraries] From b035ba72c602c6a461ab5873356e06109da55eba Mon Sep 17 00:00:00 2001 From: serras Date: Mon, 11 Dec 2023 14:27:51 +0000 Subject: [PATCH 6/6] Update API files --- .../api/android/arrow-fx-stm-compose.api | 126 ++++++++++++++++++ .../api/jvm/arrow-fx-stm-compose.api | 126 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/api/android/arrow-fx-stm-compose.api create mode 100644 arrow-libs/fx/arrow-fx-stm-compose/api/jvm/arrow-fx-stm-compose.api diff --git a/arrow-libs/fx/arrow-fx-stm-compose/api/android/arrow-fx-stm-compose.api b/arrow-libs/fx/arrow-fx-stm-compose/api/android/arrow-fx-stm-compose.api new file mode 100644 index 00000000000..b7a12ba9907 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/api/android/arrow-fx-stm-compose.api @@ -0,0 +1,126 @@ +public final class arrow/fx/stm/compose/AtomicallyKt { + public static final fun atomically (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class arrow/fx/stm/compose/BlockedIndefinitely : java/lang/Throwable { + public static final field $stable I + public fun ()V +} + +public abstract interface class arrow/fx/stm/compose/STM { + public abstract fun catch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public abstract fun check (Z)V + public abstract fun orElse (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public abstract fun peek-_Eq8ja4 (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public abstract fun read-_Eq8ja4 (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public abstract fun retry ()Ljava/lang/Void; +} + +public final class arrow/fx/stm/compose/STM$DefaultImpls { + public static fun check (Larrow/fx/stm/compose/STM;Z)V + public static fun peek-_Eq8ja4 (Larrow/fx/stm/compose/STM;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public static fun read-_Eq8ja4 (Larrow/fx/stm/compose/STM;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; +} + +public final class arrow/fx/stm/compose/STMKt { + public static final fun stm (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1; +} + +public final class arrow/fx/stm/compose/StructuresKt { + public static final fun insert (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;Ljava/lang/Object;)V + public static final fun modify (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V + public static final fun read (Landroidx/compose/runtime/MutableState;)Ljava/lang/Object; + public static final fun unsafeRead (Landroidx/compose/runtime/MutableState;)Ljava/lang/Object; + public static final fun update (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static final fun write (Landroidx/compose/runtime/MutableState;Ljava/lang/Object;)V +} + +public final class arrow/fx/stm/compose/TList { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TList; + public final fun new ()Landroidx/compose/runtime/snapshots/SnapshotStateList; +} + +public final class arrow/fx/stm/compose/TMap { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TMap; + public final fun new ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TQueue { + public static final field Companion Larrow/fx/stm/compose/TQueue$Companion; + public static final synthetic fun box-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Larrow/fx/stm/compose/TQueue; + public static fun constructor-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Landroidx/compose/runtime/snapshots/SnapshotStateList; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Landroidx/compose/runtime/snapshots/SnapshotStateList;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun flush-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/util/List; + public fun hashCode ()I + public static fun hashCode-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)I + public static final fun isEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun isNotEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun plusAssign-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V + public static final fun removeAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Lkotlin/jvm/functions/Function1;)V + public static final fun size-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/String; + public static final fun tryPeek-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public static final fun tryRead-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public final synthetic fun unbox-impl ()Landroidx/compose/runtime/snapshots/SnapshotStateList; + public static final fun write-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V + public static final fun writeFront-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V +} + +public final class arrow/fx/stm/compose/TQueue$Companion { + public final fun new-9nEOTck ()Landroidx/compose/runtime/snapshots/SnapshotStateList; +} + +public final class arrow/fx/stm/compose/TSet : java/util/Set, kotlin/jvm/internal/markers/KMutableSet { + public static final field Companion Larrow/fx/stm/compose/TSet$Companion; + public fun add (Ljava/lang/Object;)Z + public static fun add-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun addAll (Ljava/util/Collection;)Z + public static fun addAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public static final synthetic fun box-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Larrow/fx/stm/compose/TSet; + public fun clear ()V + public static fun clear-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)V + public static fun constructor-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Landroidx/compose/runtime/snapshots/SnapshotStateMap; + public fun contains (Ljava/lang/Object;)Z + public static fun contains-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun containsAll (Ljava/util/Collection;)Z + public static fun containsAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Z + public fun getSize ()I + public static fun getSize-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)I + public fun hashCode ()I + public static fun hashCode-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)I + public fun isEmpty ()Z + public static fun isEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Z + public fun iterator ()Ljava/util/Iterator; + public static fun iterator-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Ljava/util/Iterator; + public fun remove (Ljava/lang/Object;)Z + public static fun remove-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public static fun removeAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public fun retainAll (Ljava/util/Collection;)Z + public static fun retainAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public synthetic fun size ()I + public fun toArray ()[Ljava/lang/Object; + public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TSet$Companion { + public final fun new-BPBqEAU ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TVar { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TVar; + public final fun new (Ljava/lang/Object;)Landroidx/compose/runtime/MutableState; +} + diff --git a/arrow-libs/fx/arrow-fx-stm-compose/api/jvm/arrow-fx-stm-compose.api b/arrow-libs/fx/arrow-fx-stm-compose/api/jvm/arrow-fx-stm-compose.api new file mode 100644 index 00000000000..b7a12ba9907 --- /dev/null +++ b/arrow-libs/fx/arrow-fx-stm-compose/api/jvm/arrow-fx-stm-compose.api @@ -0,0 +1,126 @@ +public final class arrow/fx/stm/compose/AtomicallyKt { + public static final fun atomically (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class arrow/fx/stm/compose/BlockedIndefinitely : java/lang/Throwable { + public static final field $stable I + public fun ()V +} + +public abstract interface class arrow/fx/stm/compose/STM { + public abstract fun catch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public abstract fun check (Z)V + public abstract fun orElse (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public abstract fun peek-_Eq8ja4 (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public abstract fun read-_Eq8ja4 (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public abstract fun retry ()Ljava/lang/Void; +} + +public final class arrow/fx/stm/compose/STM$DefaultImpls { + public static fun check (Larrow/fx/stm/compose/STM;Z)V + public static fun peek-_Eq8ja4 (Larrow/fx/stm/compose/STM;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public static fun read-_Eq8ja4 (Larrow/fx/stm/compose/STM;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; +} + +public final class arrow/fx/stm/compose/STMKt { + public static final fun stm (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1; +} + +public final class arrow/fx/stm/compose/StructuresKt { + public static final fun insert (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;Ljava/lang/Object;)V + public static final fun modify (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V + public static final fun read (Landroidx/compose/runtime/MutableState;)Ljava/lang/Object; + public static final fun unsafeRead (Landroidx/compose/runtime/MutableState;)Ljava/lang/Object; + public static final fun update (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static final fun write (Landroidx/compose/runtime/MutableState;Ljava/lang/Object;)V +} + +public final class arrow/fx/stm/compose/TList { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TList; + public final fun new ()Landroidx/compose/runtime/snapshots/SnapshotStateList; +} + +public final class arrow/fx/stm/compose/TMap { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TMap; + public final fun new ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TQueue { + public static final field Companion Larrow/fx/stm/compose/TQueue$Companion; + public static final synthetic fun box-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Larrow/fx/stm/compose/TQueue; + public static fun constructor-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Landroidx/compose/runtime/snapshots/SnapshotStateList; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Landroidx/compose/runtime/snapshots/SnapshotStateList;Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun flush-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/util/List; + public fun hashCode ()I + public static fun hashCode-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)I + public static final fun isEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun isNotEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Z + public static final fun plusAssign-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V + public static final fun removeAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Lkotlin/jvm/functions/Function1;)V + public static final fun size-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/String; + public static final fun tryPeek-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public static final fun tryRead-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;)Ljava/lang/Object; + public final synthetic fun unbox-impl ()Landroidx/compose/runtime/snapshots/SnapshotStateList; + public static final fun write-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V + public static final fun writeFront-impl (Landroidx/compose/runtime/snapshots/SnapshotStateList;Ljava/lang/Object;)V +} + +public final class arrow/fx/stm/compose/TQueue$Companion { + public final fun new-9nEOTck ()Landroidx/compose/runtime/snapshots/SnapshotStateList; +} + +public final class arrow/fx/stm/compose/TSet : java/util/Set, kotlin/jvm/internal/markers/KMutableSet { + public static final field Companion Larrow/fx/stm/compose/TSet$Companion; + public fun add (Ljava/lang/Object;)Z + public static fun add-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun addAll (Ljava/util/Collection;)Z + public static fun addAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public static final synthetic fun box-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Larrow/fx/stm/compose/TSet; + public fun clear ()V + public static fun clear-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)V + public static fun constructor-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Landroidx/compose/runtime/snapshots/SnapshotStateMap; + public fun contains (Ljava/lang/Object;)Z + public static fun contains-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun containsAll (Ljava/util/Collection;)Z + public static fun containsAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Z + public fun getSize ()I + public static fun getSize-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)I + public fun hashCode ()I + public static fun hashCode-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)I + public fun isEmpty ()Z + public static fun isEmpty-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Z + public fun iterator ()Ljava/util/Iterator; + public static fun iterator-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Ljava/util/Iterator; + public fun remove (Ljava/lang/Object;)Z + public static fun remove-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public static fun removeAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public fun retainAll (Ljava/util/Collection;)Z + public static fun retainAll-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Collection;)Z + public synthetic fun size ()I + public fun toArray ()[Ljava/lang/Object; + public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Landroidx/compose/runtime/snapshots/SnapshotStateMap;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TSet$Companion { + public final fun new-BPBqEAU ()Landroidx/compose/runtime/snapshots/SnapshotStateMap; +} + +public final class arrow/fx/stm/compose/TVar { + public static final field $stable I + public static final field INSTANCE Larrow/fx/stm/compose/TVar; + public final fun new (Ljava/lang/Object;)Landroidx/compose/runtime/MutableState; +} +