Skip to content

Commit

Permalink
Merge pull request #6 from tunjid/consistentNaming
Browse files Browse the repository at this point in the history
Use mutate and mutator verb more consistently accross the API
  • Loading branch information
tunjid authored Feb 15, 2024
2 parents 2bedec6 + 7e47a82 commit 75c4b50
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 116 deletions.
8 changes: 4 additions & 4 deletions core/src/commonMain/kotlin/com/tunjid/mutator/Mutator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
package com.tunjid.mutator

/**
* Type definition for a unit of change for a type [T].
* Type definition for a unit of change for a type [State].
*/
typealias Mutation<State> = State.() -> State

typealias StateHolder<State> = StateProducer<State>
typealias StateHolder<State> = StateMutator<State>

interface StateProducer<State : Any> {
interface StateMutator<State : Any> {
val state: State
}

interface ActionStateProducer<Action : Any, State : Any> : StateProducer<State> {
interface ActionStateMutator<Action : Any, State : Any> : StateMutator<State> {
val accept: (Action) -> Unit
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.tunjid.mutator.coroutines

import com.tunjid.mutator.ActionStateProducer
import com.tunjid.mutator.ActionStateMutator
import com.tunjid.mutator.Mutation
import com.tunjid.mutator.StateHolder
import kotlinx.coroutines.CoroutineScope
Expand All @@ -33,9 +33,9 @@ import kotlinx.coroutines.launch
typealias SuspendingStateHolder<State> = StateHolder<suspend () -> State>

/**
* Defines a [ActionStateProducer] to convert a [Flow] of [Action] into a [StateFlow] of [State].
* Defines a [ActionStateMutator] to convert a [Flow] of [Action] into a [StateFlow] of [State].
*
* [this@actionStateFlowProducer]: The [CoroutineScope] for the resulting [StateFlow]. Any [Action]s sent if there are no
* [this@actionStateFlowMutator]: The [CoroutineScope] for the resulting [StateFlow]. Any [Action]s sent if there are no
* subscribers to the output [StateFlow] will suspend until there is as least one subscriber.
*
* [initialState]: The seed state for the resulting [StateFlow].
Expand All @@ -48,13 +48,13 @@ typealias SuspendingStateHolder<State> = StateHolder<suspend () -> State>
* of state that will be reduced into the [initialState]. This is often achieved through the
* [toMutationStream] [Flow] extension function.
*/
fun <Action : Any, State : Any> CoroutineScope.actionStateFlowProducer(
fun <Action : Any, State : Any> CoroutineScope.actionStateFlowMutator(
initialState: State,
started: SharingStarted = SharingStarted.WhileSubscribed(DEFAULT_STOP_TIMEOUT_MILLIS),
inputs: List<Flow<Mutation<State>>> = listOf(),
inputs: List<Flow<Mutation<State>>> = emptyList(),
stateTransform: (Flow<State>) -> Flow<State> = { it },
actionTransform: SuspendingStateHolder<State>.(Flow<Action>) -> Flow<Mutation<State>> = { emptyFlow() }
): ActionStateProducer<Action, StateFlow<State>> = ActionStateFlowProducer(
): ActionStateMutator<Action, StateFlow<State>> = ActionStateFlowMutator(
coroutineScope = this,
initialState = initialState,
started = started,
Expand All @@ -63,26 +63,26 @@ fun <Action : Any, State : Any> CoroutineScope.actionStateFlowProducer(
actionTransform = actionTransform
)

private class ActionStateFlowProducer<Action : Any, State : Any>(
private class ActionStateFlowMutator<Action : Any, State : Any>(
coroutineScope: CoroutineScope,
initialState: State,
started: SharingStarted,
inputs: List<Flow<Mutation<State>>>,
stateTransform: (Flow<State>) -> Flow<State> = { it },
actionTransform: SuspendingStateHolder<State>.(Flow<Action>) -> Flow<Mutation<State>>
) : ActionStateProducer<Action, StateFlow<State>>,
) : ActionStateMutator<Action, StateFlow<State>>,
suspend () -> State {

private val actions = Channel<Action>()

// Allows for reading the current state in concurrent contexts.
// Note that it suspends to prevent reading state before this class is fully constructed
private val stateReader = object : SuspendingStateHolder<State> {
override val state: suspend () -> State = this@ActionStateFlowProducer
override val state: suspend () -> State = this@ActionStateFlowMutator
}

override val state: StateFlow<State> =
coroutineScope.produceState(
coroutineScope.mutateState(
initialState = initialState,
started = started,
stateTransform = stateTransform,
Expand All @@ -105,10 +105,10 @@ private class ActionStateFlowProducer<Action : Any, State : Any>(
*
* This is typically useful for testing or previews
*/
fun <Action : Any, State : Any> State.asNoOpStateFlowMutator(): ActionStateProducer<Action, StateFlow<State>> =
object : ActionStateProducer<Action, StateFlow<State>> {
fun <Action : Any, State : Any> State.asNoOpStateFlowMutator(): ActionStateMutator<Action, StateFlow<State>> =
object : ActionStateMutator<Action, StateFlow<State>> {
override val accept: (Action) -> Unit = {}
override val state: StateFlow<State> = MutableStateFlow(this@asNoOpStateFlowMutator)
}

private const val DEFAULT_STOP_TIMEOUT_MILLIS = 5000L
internal const val DEFAULT_STOP_TIMEOUT_MILLIS = 5000L
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.tunjid.mutator.coroutines

import com.tunjid.mutator.Mutation
import com.tunjid.mutator.StateProducer
import com.tunjid.mutator.StateMutator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
Expand All @@ -28,11 +28,11 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

fun <State : Any> CoroutineScope.stateFlowProducer(
fun <State : Any> CoroutineScope.stateFlowMutator(
initialState: State,
started: SharingStarted = SharingStarted.WhileSubscribed(),
inputs: List<Flow<Mutation<State>>>
) = StateFlowProducer(
) = StateFlowMutator(
scope = this,
initialState = initialState,
started = started,
Expand All @@ -42,24 +42,26 @@ fun <State : Any> CoroutineScope.stateFlowProducer(
/**
* Manges state production for [State]
*/
class StateFlowProducer<State : Any> internal constructor(
class StateFlowMutator<State : Any> internal constructor(
private val scope: CoroutineScope,
initialState: State,
started: SharingStarted = SharingStarted.WhileSubscribed(),
inputs: List<Flow<Mutation<State>>>
) : StateProducer<StateFlow<State>> {
started: SharingStarted = SharingStarted.WhileSubscribed(DEFAULT_STOP_TIMEOUT_MILLIS),
inputs: List<Flow<Mutation<State>>> = emptyList(),
stateTransform: (Flow<State>) -> Flow<State> = { it },
) : StateMutator<StateFlow<State>> {
private val mutationChannel = Channel<Mutation<State>>()
private val mutationSender = FlowCollector(mutationChannel::send)

override val state = scope.produceState(
override val state = scope.mutateState(
initialState = initialState,
started = started,
stateTransform = stateTransform,
inputs = inputs + mutationChannel.receiveAsFlow()
)

/**
* Runs [block] in parallel with any other tasks submitted to [launch]. [block] is only ever run if there is an
* active collector of [state], and is managed under the [SharingStarted] policy passed to this [StateProducer].
* active collector of [state], and is managed under the [SharingStarted] policy passed to this [StateMutator].
*
* If there are no observers of [state] at the invocation of [launch], the Coroutine launched will suspend till
* a collector begins to collect from [state].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.tunjid.mutator.coroutines
import com.tunjid.mutator.Mutation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emitAll
Expand All @@ -28,17 +27,16 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/**
* Produces a [StateFlow] by merging [inputs] and reducing them into an
* [initialState] state within [this] [CoroutineScope]
*/
fun <State : Any> CoroutineScope.produceState(
fun <State : Any> CoroutineScope.mutateState(
initialState: State,
started: SharingStarted,
stateTransform: (Flow<State>) -> Flow<State> = { it },
inputs: List<Flow<Mutation<State>>>
inputs: List<Flow<Mutation<State>>>,
stateTransform: (Flow<State>) -> Flow<State> = { it }
): StateFlow<State> {
// Set the seed for the state
var seed = initialState
Expand All @@ -63,18 +61,5 @@ fun <State : Any> CoroutineScope.produceState(
)
}


fun <State : Any> Flow<Mutation<State>>.reduceInto(initialState: State): Flow<State> =
scan(initialState) { state, mutation -> mutation(state) }

/**
* Helper function to run the provided [block] in the [scope]
*/
fun <T : Any> MutableSharedFlow<Mutation<T>>.withScope(
scope: CoroutineScope,
block: suspend MutableSharedFlow<Mutation<T>>.() -> Unit
) {
scope.launch {
block()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.tunjid.mutator.coroutines

import app.cash.turbine.test
import com.tunjid.mutator.ActionStateProducer
import com.tunjid.mutator.ActionStateMutator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand All @@ -37,7 +37,7 @@ import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals

class ActionStateProducerKtTest {
class ActionStateMutatorKtTest {

private val testDispatcher = UnconfinedTestDispatcher()

Expand All @@ -52,10 +52,10 @@ class ActionStateProducerKtTest {
}

@Test
fun actionStateFlowProducerRemembersLastValue() = runTest {
fun actionStateFlowMutatorRemembersLastValue() = runTest {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

val mutator = scope.actionStateFlowProducer<IntAction, State>(
val mutator = scope.actionStateFlowMutator<IntAction, State>(
initialState = State(),
started = SharingStarted.WhileSubscribed(),
actionTransform = { actions ->
Expand Down Expand Up @@ -103,11 +103,11 @@ class ActionStateProducerKtTest {
}

@Test
fun actionStateFlowProducerSuspendsWithNoSubscribers() = runTest {
fun actionStateFlowMutatorSuspendsWithNoSubscribers() = runTest {
val dispatcher = StandardTestDispatcher()
val scope = CoroutineScope(SupervisorJob() + dispatcher)

val mutator = scope.actionStateFlowProducer<IntAction, State>(
val mutator = scope.actionStateFlowMutator<IntAction, State>(
initialState = State(),
started = SharingStarted.WhileSubscribed(),
actionTransform = { actions ->
Expand Down Expand Up @@ -155,12 +155,12 @@ class ActionStateProducerKtTest {

@Test
fun noOpOperatorCompiles() {
val noOpActionStateProducer: ActionStateProducer<Action, StateFlow<State>> = State().asNoOpStateFlowMutator()
noOpActionStateProducer.accept(IntAction.Add(value = 1))
val noOpActionStateMutator: ActionStateMutator<Action, StateFlow<State>> = State().asNoOpStateFlowMutator()
noOpActionStateMutator.accept(IntAction.Add(value = 1))

assertEquals(
expected = State(),
actual = noOpActionStateProducer.state.value
actual = noOpActionStateMutator.state.value
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class StateProductionKtTest {

@Test
fun test_simple_state_production() = runTest {
val state = scope.produceState(
val state = scope.mutateState(
initialState = State(),
started = SharingStarted.WhileSubscribed(),
inputs = listOf(
Expand All @@ -76,7 +76,7 @@ class StateProductionKtTest {

@Test
fun test_state_production_persists_after_unsubscribing() = runTest {
val state = scope.produceState(
val state = scope.mutateState(
initialState = State(),
started = SharingStarted.WhileSubscribed(),
inputs = listOf(
Expand All @@ -102,7 +102,7 @@ class StateProductionKtTest {

@Test
fun test_state_production_with_merged_flows() = runTest {
val state = scope.produceState(
val state = scope.mutateState(
initialState = State(),
started = SharingStarted.WhileSubscribed(),
inputs = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ The merge approach can be formalized into an extension function on the `Coroutin
""".trimIndent()

private val twoCode = """
fun <State: Any> CoroutineScope.stateFlowProducer(
fun <State: Any> CoroutineScope.stateFlowMutator(
initialState: State,
started: SharingStarted = SharingStarted.WhileSubscribed(),
inputs: List<Flow<Mutation<State>>>
) : StateFlowProducer<State>
) : StateFlowMutator<State>
""".trimIndent()

private val threeMarkdown = """
Expand All @@ -60,7 +60,7 @@ class Snail7StateHolder(
private val progressChanges: Flow<Mutation<Snail7State>> = …
private val stateProducer = scope.stateFlowProducer(
private val stateMutator = scope.stateFlowMutator(
initialState = Snail7State(),
started = SharingStarted.WhileSubscribed(),
inputs = listOf(
Expand All @@ -69,13 +69,13 @@ class Snail7StateHolder(
)
)
val state: StateFlow<Snail7State> = stateProducer.state
val state: StateFlow<Snail7State> = stateMutator.state
fun setSnailColor(index: Int) = stateProducer.launch {
fun setSnailColor(index: Int) = stateMutator.launch {
emit { copy(color = colors[index]) }
}
fun setProgress(progress: Float) = stateProducer.launch {
fun setProgress(progress: Float) = stateMutator.launch {
emit { copy(progress = progress) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ class Snail8StateHolder(
private val scope: CoroutineScope
) {
private val stateProducer = scope.stateFlowProducer(
private val stateMutator = scope.stateFlowMutator(
...
)
...
fun setMode(isDark: Boolean) = stateProducer.launch {
fun setMode(isDark: Boolean) = stateMutator.launch {
emit { copy(isDark = isDark) }
/* Collect from a flow that animates color changes */
interpolateColors(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ class Snail9StateHolder(
private val scope: CoroutineScope
) {
private val stateProducer = scope.stateFlowProducer(
private val stateMutator = scope.stateFlowMutator(
...
)
...
fun setMode(isDark: Boolean) = stateProducer.launch {
fun setMode(isDark: Boolean) = stateMutator.launch {
if (state.value.isInterpolating) return@launch
emit { copy(isDark = isDark, isInterpolating = true) }
interpolateColors(
Expand Down Expand Up @@ -107,13 +107,13 @@ class Snail10StateHolder(
private val scope: CoroutineScope
) {
private val stateProducer = scope.stateFlowProducer(
private val stateMutator = scope.stateFlowMutator(
...
)
private var setModeJob: Job? = null
...
fun setMode(isDark: Boolean) = stateProducer.launch {
fun setMode(isDark: Boolean) = stateMutator.launch {
setModeJob?.cancel()
setModeJob = currentCoroutineContext()[Job]
mutate { copy(isDark = isDark) }
Expand Down Expand Up @@ -177,7 +177,7 @@ class Snail11StateHolder(
private val progressChanges: Flow<Mutation<Snail11State>> = …
private val mutator = scope.actionStateFlowProducer<Action, Snail11State>(
private val mutator = scope.actionStateFlowMutator<Action, Snail11State>(
initialState = Snail11State(),
started = SharingStarted.WhileSubscribed(),
inputs = listOf(
Expand Down
Loading

0 comments on commit 75c4b50

Please sign in to comment.