From 4553dcd7bfbb4abae71746453aeace98d267b44e Mon Sep 17 00:00:00 2001
From: Dmitriy Berdnikov <d.k.berdnikov@tinkoff.ru>
Date: Mon, 19 Feb 2024 12:48:42 +0300
Subject: [PATCH] leave only Dsl way to write state reducer

---
 .../vivid/elmslie/core/store/NoOpReducer.kt   |   5 +-
 .../vivid/elmslie/core/store/ScreenReducer.kt |  23 ++++
 .../vivid/elmslie/core/store/StateReducer.kt  |  11 +-
 .../elmslie/core/store/dsl/DslReducer.kt      |  14 ---
 .../core/store/dsl/ScreenDslReducer.kt        |  31 -----
 .../core/store/EffectCachingElmStoreTest.kt   |  60 +++++----
 .../vivid/elmslie/core/store/ElmStoreTest.kt  | 118 ++++++++++--------
 .../elmslie/core/store/dsl/DslReducerTest.kt  |   3 +-
 ...DslReducerTest.kt => ScreenReducerTest.kt} |  26 ++--
 .../coroutines/timer/elm/TimerReducer.kt      |   4 +-
 .../elmslie/samples/calculator/Models.kt      |   2 +
 .../vivid/elmslie/samples/calculator/Store.kt |  48 +++----
 12 files changed, 175 insertions(+), 170 deletions(-)
 create mode 100644 elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/ScreenReducer.kt
 delete mode 100644 elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/DslReducer.kt
 delete mode 100644 elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducer.kt
 rename elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/{ScreenDslReducerTest.kt => ScreenReducerTest.kt} (76%)

diff --git a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/NoOpReducer.kt b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/NoOpReducer.kt
index 2957dcc6..f84b1c2e 100644
--- a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/NoOpReducer.kt
+++ b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/NoOpReducer.kt
@@ -3,7 +3,8 @@ package money.vivid.elmslie.core.store
 /**
  * Reducer that doesn't change state, and doesn't emit commands or effects
  */
-class NoOpReducer<Event : Any, State : Any, Effect : Any, Command : Any> : StateReducer<Event, State, Effect, Command> {
+class NoOpReducer<Event : Any, State : Any, Effect : Any, Command : Any> :
+    StateReducer<Event, State, Effect, Command>() {
 
-    override fun reduce(event: Event, state: State) = Result<State, Effect, Command>(state)
+    override fun Result.reduce(event: Event) = Unit
 }
diff --git a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/ScreenReducer.kt b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/ScreenReducer.kt
new file mode 100644
index 00000000..ddb7efd5
--- /dev/null
+++ b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/ScreenReducer.kt
@@ -0,0 +1,23 @@
+package money.vivid.elmslie.core.store
+
+import kotlin.reflect.KClass
+
+abstract class ScreenReducer<Event : Any, Ui : Any, Internal : Any, State : Any, Effect : Any, Command : Any>(
+    private val uiEventClass: KClass<Ui>,
+    private val internalEventClass: KClass<Internal>
+) : StateReducer<Event, State, Effect, Command>() {
+
+
+    protected abstract fun Result.ui(event: Ui): Any?
+
+    protected abstract fun Result.internal(event: Internal): Any?
+
+    override fun Result.reduce(event: Event) {
+        @Suppress("UNCHECKED_CAST")
+        when {
+            uiEventClass.isInstance(event) -> ui(event as Ui)
+            internalEventClass.isInstance(event) -> internal(event as Internal)
+            else -> error("Event ${event::class} is neither UI nor Internal")
+        }
+    }
+}
diff --git a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/StateReducer.kt b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/StateReducer.kt
index 8bc43319..9ee2f85d 100644
--- a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/StateReducer.kt
+++ b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/StateReducer.kt
@@ -1,6 +1,13 @@
 package money.vivid.elmslie.core.store
 
-fun interface StateReducer<Event : Any, State : Any, Effect : Any, Command : Any> {
+import money.vivid.elmslie.core.store.dsl.ResultBuilder
 
-    fun reduce(event: Event, state: State): Result<State, Effect, Command>
+abstract class StateReducer<Event : Any, State : Any, Effect : Any, Command : Any> {
+
+    // Needed to type less code
+    protected inner class Result(state: State) : ResultBuilder<State, Effect, Command>(state)
+
+    protected abstract fun Result.reduce(event: Event)
+
+    fun reduce(event: Event, state: State) = Result(state).apply { reduce(event) }.build()
 }
diff --git a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/DslReducer.kt b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/DslReducer.kt
deleted file mode 100644
index 2d67d09c..00000000
--- a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/DslReducer.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package money.vivid.elmslie.core.store.dsl
-
-import money.vivid.elmslie.core.store.StateReducer
-
-abstract class DslReducer<Event : Any, State : Any, Effect : Any, Command : Any> :
-    StateReducer<Event, State, Effect, Command> {
-
-    // Needed to type less code
-    protected inner class Result(state: State) : ResultBuilder<State, Effect, Command>(state)
-
-    protected abstract fun Result.reduce(event: Event): Any?
-
-    final override fun reduce(event: Event, state: State) = Result(state).apply { reduce(event) }.build()
-}
diff --git a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducer.kt b/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducer.kt
deleted file mode 100644
index e50f073c..00000000
--- a/elmslie-core/src/main/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducer.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package money.vivid.elmslie.core.store.dsl
-
-import money.vivid.elmslie.core.store.Result
-import money.vivid.elmslie.core.store.StateReducer
-import kotlin.reflect.KClass
-
-abstract class ScreenDslReducer<Event : Any, Ui : Any, Internal : Any, State : Any, Effect : Any, Command : Any>(
-    private val uiEventClass: KClass<Ui>,
-    private val internalEventClass: KClass<Internal>
-) : StateReducer<Event, State, Effect, Command> {
-
-    protected inner class Result(state: State) : ResultBuilder<State, Effect, Command>(state)
-
-    protected abstract fun Result.ui(event: Ui): Any?
-
-    protected abstract fun Result.internal(event: Internal): Any?
-
-    final override fun reduce(
-        event: Event,
-        state: State
-    ): money.vivid.elmslie.core.store.Result<State, Effect, Command> {
-        val body = Result(state)
-        @Suppress("UNCHECKED_CAST")
-        when {
-            uiEventClass.isInstance(event) -> body.ui(event as Ui)
-            internalEventClass.isInstance(event) -> body.internal(event as Internal)
-            else -> error("Event ${event::class} is neither UI nor Internal")
-        }
-        return body.build()
-    }
-}
diff --git a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/EffectCachingElmStoreTest.kt b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/EffectCachingElmStoreTest.kt
index d433e218..93446022 100644
--- a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/EffectCachingElmStoreTest.kt
+++ b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/EffectCachingElmStoreTest.kt
@@ -40,14 +40,13 @@ class EffectCachingElmStoreTest {
     fun `Should collect effects which are emitted before collecting flow`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state,
-                            effect = Effect(event.value),
-                        )
-                    },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
+                    }
+                },
+            )
                 .toCachedStore()
 
         store.start()
@@ -76,14 +75,13 @@ class EffectCachingElmStoreTest {
     fun `Should collect effects which are emitted before collecting flow and after`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state,
-                            effect = Effect(event.value),
-                        )
-                    },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
+                    }
+                },
+            )
                 .toCachedStore()
 
         store.start()
@@ -114,14 +112,13 @@ class EffectCachingElmStoreTest {
     fun `Should emit effects from cache only for the first subscriber`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state,
-                            effect = Effect(event.value),
-                        )
-                    },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
+                    }
+                },
+            )
                 .toCachedStore()
 
         store.start()
@@ -152,14 +149,13 @@ class EffectCachingElmStoreTest {
     fun `Should cache effects if there is no left collectors`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state,
-                            effect = Effect(event.value),
-                        )
-                    },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
+                    }
+                },
+            )
                 .toCachedStore()
 
         store.start()
diff --git a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/ElmStoreTest.kt b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/ElmStoreTest.kt
index b0c87fee..7b0098a5 100644
--- a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/ElmStoreTest.kt
+++ b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/ElmStoreTest.kt
@@ -57,15 +57,18 @@ class ElmStoreTest {
             override fun execute(command: Command): Flow<Event> =
                 flow { emit(Event()) }.onEach { delay(1000) }
         }
+
         val store =
             store(
-                    state = State(),
-                    reducer = { _, state ->
-                        Result(state = state.copy(value = state.value + 1), command = Command())
-                    },
-                    actor = actor,
-                )
-                .start()
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        state { copy(value = state.value + 1) }
+                        commands { +Command() }
+                    }
+                },
+                actor = actor,
+            ).start()
 
         val emittedStates = mutableListOf<State>()
         val collectJob = launch { store.states.toList(emittedStates) }
@@ -91,9 +94,13 @@ class ElmStoreTest {
     fun `Should update state when event is received`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state -> Result(state = state.copy(value = event.value)) },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        state { copy(value = event.value) }
+                    }
+                },
+            )
                 .start()
 
         assertEquals(
@@ -110,9 +117,13 @@ class ElmStoreTest {
     fun `Should not update state when it's equal to previous one`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state -> Result(state = state.copy(value = event.value)) },
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        state { copy(value = event.value) }
+                    }
+                },
+            )
                 .start()
 
         val emittedStates = mutableListOf<State>()
@@ -134,11 +145,13 @@ class ElmStoreTest {
     fun `Should collect all emitted effects`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(state = state, effect = Effect(value = event.value))
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
                     }
-                )
+                },
+            )
                 .start()
 
         val effects = mutableListOf<Effect>()
@@ -161,11 +174,13 @@ class ElmStoreTest {
     fun `Should skip the effect which is emitted before subscribing to effects`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(state = state, effect = Effect(value = event.value))
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
                     }
-                )
+                },
+            )
                 .start()
 
         val effects = mutableListOf<Effect>()
@@ -188,19 +203,16 @@ class ElmStoreTest {
     fun `Should collect all effects emitted once per time`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state,
-                            commands = emptyList(),
-                            effects =
-                                listOf(
-                                    Effect(value = event.value),
-                                    Effect(value = event.value),
-                                ),
-                        )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects {
+                            +Effect(value = event.value)
+                            +Effect(value = event.value)
+                        }
                     }
-                )
+                },
+            )
                 .start()
 
         val effects = mutableListOf<Effect>()
@@ -222,11 +234,13 @@ class ElmStoreTest {
     fun `Should collect all emitted effects by all collectors`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(state = state, effect = Effect(value = event.value))
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
                     }
-                )
+                },
+            )
                 .start()
 
         val effects1 = mutableListOf<Effect>()
@@ -259,11 +273,13 @@ class ElmStoreTest {
     fun `Should collect duplicated effects`() = runTest {
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(state = state, effect = Effect(value = event.value))
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        effects { +Effect(value = event.value) }
                     }
-                )
+                },
+            )
                 .start()
 
         val effects = mutableListOf<Effect>()
@@ -289,15 +305,17 @@ class ElmStoreTest {
         }
         val store =
             store(
-                    state = State(),
-                    reducer = { event, state ->
-                        Result(
-                            state = state.copy(value = event.value),
-                            command = Command(event.value - 1).takeIf { event.value > 0 }
-                        )
-                    },
-                    actor = actor,
-                )
+                state = State(),
+                reducer = object : StateReducer<Event, State, Effect, Command>() {
+                    override fun Result.reduce(event: Event) {
+                        state { copy(value = event.value) }
+                        commands {
+                            +Command(event.value - 1).takeIf { event.value > 0 }
+                        }
+                    }
+                },
+                actor = actor,
+            )
                 .start()
 
         val states = mutableListOf<State>()
diff --git a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/DslReducerTest.kt b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/DslReducerTest.kt
index 82c25634..3fd9f4da 100644
--- a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/DslReducerTest.kt
+++ b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/DslReducerTest.kt
@@ -1,10 +1,11 @@
 package money.vivid.elmslie.core.store.dsl
 
+import money.vivid.elmslie.core.store.StateReducer
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 
-private object BasicDslReducer : DslReducer<TestEvent, TestState, TestEffect, TestCommand>() {
+private object BasicDslReducer : StateReducer<TestEvent, TestState, TestEffect, TestCommand>() {
 
     override fun Result.reduce(event: TestEvent) =
         when (event) {
diff --git a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducerTest.kt b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenReducerTest.kt
similarity index 76%
rename from elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducerTest.kt
rename to elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenReducerTest.kt
index 14fbf9f5..ea80efde 100644
--- a/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenDslReducerTest.kt
+++ b/elmslie-core/src/test/kotlin/money/vivid/elmslie/core/store/dsl/ScreenReducerTest.kt
@@ -1,18 +1,20 @@
 package money.vivid.elmslie.core.store.dsl
 
+import money.vivid.elmslie.core.store.ScreenReducer
+import money.vivid.elmslie.core.store.StateReducer
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 
-object BasicScreenDslReducer :
-    ScreenDslReducer<
-        TestScreenEvent,
-        TestScreenEvent.Ui,
-        TestScreenEvent.Internal,
-        TestState,
-        TestEffect,
-        TestCommand
-    >(TestScreenEvent.Ui::class, TestScreenEvent.Internal::class) {
+object BasicScreenReducer :
+    ScreenReducer<
+            TestScreenEvent,
+            TestScreenEvent.Ui,
+            TestScreenEvent.Internal,
+            TestState,
+            TestEffect,
+            TestCommand
+            >(TestScreenEvent.Ui::class, TestScreenEvent.Internal::class) {
 
     override fun Result.ui(event: TestScreenEvent.Ui) =
         when (event) {
@@ -30,7 +32,7 @@ object BasicScreenDslReducer :
 }
 
 // The same code
-object PlainScreenDslReducer : DslReducer<TestScreenEvent, TestState, TestEffect, TestCommand>() {
+object PlainScreenDslReducer : StateReducer<TestScreenEvent, TestState, TestEffect, TestCommand>() {
 
     override fun Result.reduce(event: TestScreenEvent) =
         when (event) {
@@ -53,9 +55,9 @@ object PlainScreenDslReducer : DslReducer<TestScreenEvent, TestState, TestEffect
         }
 }
 
-internal class ScreenDslReducerTest {
+internal class ScreenReducerTest {
 
-    private val reducer = BasicScreenDslReducer
+    private val reducer = BasicScreenReducer
 
     @Test
     fun `Ui event is executed`() {
diff --git a/samples/coroutines-loader/src/main/kotlin/money/vivid/elmslie/samples/coroutines/timer/elm/TimerReducer.kt b/samples/coroutines-loader/src/main/kotlin/money/vivid/elmslie/samples/coroutines/timer/elm/TimerReducer.kt
index 769f5f1d..1cdf42a9 100644
--- a/samples/coroutines-loader/src/main/kotlin/money/vivid/elmslie/samples/coroutines/timer/elm/TimerReducer.kt
+++ b/samples/coroutines-loader/src/main/kotlin/money/vivid/elmslie/samples/coroutines/timer/elm/TimerReducer.kt
@@ -1,9 +1,9 @@
 package money.vivid.elmslie.samples.coroutines.timer.elm
 
-import money.vivid.elmslie.core.store.dsl.DslReducer
+import money.vivid.elmslie.core.store.StateReducer
 import java.util.UUID
 
-internal object TimerReducer : DslReducer<Event, State, Effect, Command>() {
+internal object TimerReducer : StateReducer<Event, State, Effect, Command>() {
 
     override fun Result.reduce(event: Event) =
         when (event) {
diff --git a/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Models.kt b/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Models.kt
index 293466d8..0d02bc6c 100644
--- a/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Models.kt
+++ b/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Models.kt
@@ -17,6 +17,8 @@ data class State(
     val input: Int = 0
 )
 
+sealed interface Command
+
 enum class Operation(
     op: (Int, Int) -> Int
 ) : (Int, Int) -> Int by op {
diff --git a/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Store.kt b/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Store.kt
index 47000a5d..95d903d8 100644
--- a/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Store.kt
+++ b/samples/kotlin-calculator/src/main/kotlin/money/vivid/elmslie/samples/calculator/Store.kt
@@ -3,35 +3,35 @@ package money.vivid.elmslie.samples.calculator
 import money.vivid.elmslie.core.store.ElmStore
 import money.vivid.elmslie.core.store.NoOpActor
 import money.vivid.elmslie.core.store.StateReducer
-import money.vivid.elmslie.core.store.Result
-
 
 private const val MAX_INPUT_LENGTH = 9
 
-val Reducer = StateReducer { event: Event, state: State ->
-    when (event) {
-        is Event.EnterDigit -> when {
-            state.input.toString().length == MAX_INPUT_LENGTH -> {
-                Result(state, effect = Effect.NotifyError("Reached max input length"))
+val Reducer = object : StateReducer<Event, State, Effect, Command>() {
+    override fun Result.reduce(event: Event) {
+        when (event) {
+            is Event.EnterDigit -> when {
+                state.input.toString().length == MAX_INPUT_LENGTH -> {
+                    effects { +Effect.NotifyError("Reached max input length") }
+                }
+
+                event.digit.isDigit() -> {
+                    state { copy(input = "${state.input}${event.digit}".toInt()) }
+                }
+
+                else -> effects { +Effect.NotifyError("${event.digit} is not a digit") }
+            }
+
+            is Event.PerformOperation -> {
+                val total = state.pendingOperation?.invoke(state.total, state.input) ?: state.total
+                state { copy(pendingOperation = event.operation, total = total, input = 0) }
+                effects { +Effect.NotifyNewResult(total) }
             }
-            event.digit.isDigit() -> {
-                Result(state.copy(input = "${state.input}${event.digit}".toInt()))
+
+            is Event.Evaluate -> {
+                val total = state.pendingOperation?.invoke(state.total, state.input) ?: state.total
+                state { copy(pendingOperation = null, total = total, input = 0) }
+                effects { +Effect.NotifyNewResult(total) }
             }
-            else -> Result(state, effect = Effect.NotifyError("${event.digit} is not a digit"))
-        }
-        is Event.PerformOperation -> {
-            val total = state.pendingOperation?.invoke(state.total, state.input) ?: state.total
-            Result(
-                state = state.copy(pendingOperation = event.operation, total = total, input = 0),
-                effect = Effect.NotifyNewResult(total)
-            )
-        }
-        is Event.Evaluate -> {
-            val total = state.pendingOperation?.invoke(state.total, state.input) ?: state.total
-            Result(
-                state = state.copy(pendingOperation = null, total = total, input = 0),
-                effect = Effect.NotifyNewResult(total)
-            )
         }
     }
 }