-
Notifications
You must be signed in to change notification settings - Fork 448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arrow Optics ❤️ Compose #3299
Arrow Optics ❤️ Compose #3299
Changes from all commits
84d64c0
2270eb2
607dc71
ce0f9c6
9bc65ae
4b24494
a424188
eee3786
2721856
0947c3a
24f9d79
3ac8587
aeadb48
7505214
132bce0
b5f037f
0e99a6d
1f1a0ff
f2d5428
162bddc
16b23ae
93810a6
bfaf2c2
d700c77
f474b39
452aa99
047b905
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
public final class arrow/optics/CopyKt { | ||
public static final fun update (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V | ||
public static final fun updateCopy (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V | ||
public static final fun updateCopy (Lkotlinx/coroutines/flow/MutableStateFlow;Lkotlin/jvm/functions/Function1;)V | ||
} | ||
|
||
public final class arrow/optics/FlowKt { | ||
public static final fun optic (Lkotlinx/coroutines/flow/MutableStateFlow;Larrow/optics/PLens;)Lkotlinx/coroutines/flow/MutableStateFlow; | ||
public static final fun optic (Lkotlinx/coroutines/flow/SharedFlow;Larrow/optics/Getter;)Lkotlinx/coroutines/flow/SharedFlow; | ||
public static final fun optic (Lkotlinx/coroutines/flow/StateFlow;Larrow/optics/Getter;)Lkotlinx/coroutines/flow/StateFlow; | ||
} | ||
|
||
public final class arrow/optics/StateKt { | ||
public static final fun optic (Landroidx/compose/runtime/MutableState;Larrow/optics/PLens;)Landroidx/compose/runtime/MutableState; | ||
public static final fun optic (Landroidx/compose/runtime/State;Larrow/optics/Getter;)Landroidx/compose/runtime/State; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
public final class arrow/optics/CopyKt { | ||
public static final fun update (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V | ||
public static final fun updateCopy (Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function1;)V | ||
public static final fun updateCopy (Lkotlinx/coroutines/flow/MutableStateFlow;Lkotlin/jvm/functions/Function1;)V | ||
} | ||
|
||
public final class arrow/optics/FlowKt { | ||
public static final fun optic (Lkotlinx/coroutines/flow/MutableStateFlow;Larrow/optics/PLens;)Lkotlinx/coroutines/flow/MutableStateFlow; | ||
public static final fun optic (Lkotlinx/coroutines/flow/SharedFlow;Larrow/optics/Getter;)Lkotlinx/coroutines/flow/SharedFlow; | ||
public static final fun optic (Lkotlinx/coroutines/flow/StateFlow;Larrow/optics/Getter;)Lkotlinx/coroutines/flow/StateFlow; | ||
} | ||
|
||
public final class arrow/optics/StateKt { | ||
public static final fun optic (Landroidx/compose/runtime/MutableState;Larrow/optics/PLens;)Landroidx/compose/runtime/MutableState; | ||
public static final fun optic (Landroidx/compose/runtime/State;Larrow/optics/Getter;)Landroidx/compose/runtime/State; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
@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(projects.arrowOptics) | ||
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<Test>().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("2.0.0-Beta1")) | ||
kotlinCompilerPluginArgs.add("suppressKotlinVersionCompatibilityCheck=$kotlinVersion") | ||
} | ||
} | ||
|
||
android { | ||
namespace = "arrow.optics.compose" | ||
compileSdk = libs.versions.android.compileSdk.get().toInt() | ||
} | ||
|
||
tasks.named<Jar>("jvmJar").configure { | ||
manifest { | ||
attributes["Automatic-Module-Name"] = "arrow.optics.compose" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Maven publishing configuration | ||
pom.name=Arrow Optics for Compose | ||
# Build configuration | ||
kapt.incremental.apt=false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package arrow.optics | ||
|
||
import androidx.compose.runtime.MutableState | ||
import androidx.compose.runtime.snapshots.Snapshot | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.update | ||
|
||
/** | ||
* Modifies the value in this [MutableState] | ||
* by applying the function [block] to the current value. | ||
*/ | ||
public inline fun <T> MutableState<T>.update(crossinline block: (T) -> T) { | ||
Snapshot.withMutableSnapshot { | ||
serras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
value = block(value) | ||
} | ||
} | ||
|
||
/** | ||
* Modifies the value in this [MutableState] | ||
* by performing the operations in the [Copy] [block]. | ||
*/ | ||
public fun <T> MutableState<T>.updateCopy(block: Copy<T>.() -> Unit) { | ||
update { it.copy(block) } | ||
} | ||
|
||
/** | ||
* Updates the value in this [MutableStateFlow] | ||
* by performing the operations in the [Copy] [block]. | ||
*/ | ||
public fun <T> MutableStateFlow<T>.updateCopy(block: Copy<T>.() -> Unit) { | ||
update { it.copy(block) } | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||
package arrow.optics | ||||||
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||||||
import kotlinx.coroutines.flow.FlowCollector | ||||||
import kotlinx.coroutines.flow.MutableStateFlow | ||||||
import kotlinx.coroutines.flow.SharedFlow | ||||||
import kotlinx.coroutines.flow.StateFlow | ||||||
|
||||||
/** | ||||||
* Exposes the values of [this] through the optic. | ||||||
*/ | ||||||
public fun <T, A> SharedFlow<T>.optic(g: Getter<T, A>): SharedFlow<A> = object : SharedFlow<A> { | ||||||
override suspend fun collect(collector: FlowCollector<A>): Nothing = | ||||||
this@optic.collect { collector.emit(g.get(it)) } | ||||||
|
||||||
override val replayCache: List<A> | ||||||
get() = this@optic.replayCache.map { g.get(it) } | ||||||
} | ||||||
|
||||||
/** | ||||||
* Exposes the values of [this] through the optic. | ||||||
*/ | ||||||
public fun <T, A> StateFlow<T>.optic(g: Getter<T, A>): StateFlow<A> = object : StateFlow<A> { | ||||||
override val value: A | ||||||
get() = g.get(this@optic.value) | ||||||
|
||||||
override suspend fun collect(collector: FlowCollector<A>): Nothing = | ||||||
this@optic.collect { collector.emit(g.get(it)) } | ||||||
|
||||||
override val replayCache: List<A> | ||||||
get() = this@optic.replayCache.map { g.get(it) } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always contains single element.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to keep this implementation, since it makes it more clear that we are just reusing the |
||||||
} | ||||||
|
||||||
/** | ||||||
* Exposes the values of [this] through the optic. | ||||||
* Any change made to [value] is reflected in the original [MutableStateFlow]. | ||||||
*/ | ||||||
public fun <T, A> MutableStateFlow<T>.optic(lens: Lens<T, A>): MutableStateFlow<A> = object : MutableStateFlow<A> { | ||||||
override var value: A | ||||||
get() = lens.get(this@optic.value) | ||||||
set(newValue) { | ||||||
this@optic.value = lens.set(this@optic.value, newValue) | ||||||
} | ||||||
|
||||||
override suspend fun collect(collector: FlowCollector<A>): Nothing = | ||||||
this@optic.collect { collector.emit(lens.get(it)) } | ||||||
|
||||||
override fun compareAndSet(expect: A, update: A): Boolean { | ||||||
val expectT = lens.set(this@optic.value, expect) | ||||||
val updateT = lens.set(this@optic.value, update) | ||||||
return compareAndSet(expectT, updateT) | ||||||
} | ||||||
|
||||||
override fun tryEmit(value: A): Boolean = | ||||||
this@optic.tryEmit(lens.set(this@optic.value, value)) | ||||||
|
||||||
override suspend fun emit(value: A): Unit = | ||||||
this@optic.emit(lens.set(this@optic.value, value)) | ||||||
|
||||||
override val subscriptionCount: StateFlow<Int> | ||||||
get() = this@optic.subscriptionCount | ||||||
|
||||||
override val replayCache: List<A> | ||||||
get() = this@optic.replayCache.map { lens.get(it) } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above 🙏 |
||||||
|
||||||
@ExperimentalCoroutinesApi | ||||||
override fun resetReplayCache() { | ||||||
this@optic.resetReplayCache() | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package arrow.optics | ||
|
||
import androidx.compose.runtime.MutableState | ||
import androidx.compose.runtime.State | ||
|
||
/** | ||
* Exposes the value of [this] through the optic. | ||
*/ | ||
public fun <T, A> State<T>.optic(g: Getter<T, A>): State<A> = object : State<A> { | ||
override val value: A | ||
get() = g.get(this@optic.value) | ||
} | ||
|
||
/** | ||
* Exposes the value of [this] through the optic. | ||
* Any change made to [value] is reflected in the original [MutableState]. | ||
*/ | ||
public fun <T, A> MutableState<T>.optic(lens: Lens<T, A>): MutableState<A> = object : MutableState<A> { | ||
override var value: A | ||
get() = lens.get(this@optic.value) | ||
set(newValue) { | ||
this@optic.value = lens.set(this@optic.value, newValue) | ||
} | ||
|
||
override fun component1(): A = value | ||
override fun component2(): (A) -> Unit = { value = it } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Off-topic: @serras how do you feel about doing this explicitly in Arrow instead of relying on the
arrowGradleConfig
?Arrow Gradle Config came to live because of publishing, and I've in favor of replacing Arrow Gradle Config Nexus/Publish with https://github.com/vanniktech/gradle-maven-publish-plugin. I've tried it on a couple of my projects and it works better as what we have whilst supporting the same style of configuration we currently use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds great (although having to define all the targets on each project seems tiresome). Maybe another thing to do in the
arrow-2
branch?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was wondering if we could do something interesting in
buildSrc
or something where we can define some top-level functions to avoid all the repetitive boilerplate. arrow-2 sounds good! Definitely not for 1.2.2 😅