diff --git a/formula/src/main/java/com/instacart/formula/Formula.kt b/formula/src/main/java/com/instacart/formula/Formula.kt index 4f9db778c..f4a020e65 100644 --- a/formula/src/main/java/com/instacart/formula/Formula.kt +++ b/formula/src/main/java/com/instacart/formula/Formula.kt @@ -62,4 +62,8 @@ interface Formula : IFormula { override fun implementation(): Formula { return this } + + companion object { + // Used to attach extension functions. + } } diff --git a/formula/src/main/java/com/instacart/formula/FormulaExtensions.kt b/formula/src/main/java/com/instacart/formula/FormulaExtensions.kt new file mode 100644 index 000000000..46cc44bdf --- /dev/null +++ b/formula/src/main/java/com/instacart/formula/FormulaExtensions.kt @@ -0,0 +1,64 @@ +package com.instacart.formula + +inline fun Formula.Companion.stateless( + crossinline output: (Input, FormulaContext) -> Evaluation +): IFormula = DelegateFormula( + initialState = { Unit }, + evaluate = { input, _, context -> + output(input, context) + } +) + +inline fun Formula.Companion.stateless( + crossinline output: (FormulaContext) -> Evaluation +): IFormula = DelegateFormula( + initialState = { Unit }, + evaluate = { input, state, context -> + output(context) + } +) + +fun Formula.Companion.create( + initialState: (Input) -> State, + onInputChanged: ((Input, Input, State) -> State)? = null, + evaluate: (Input, State, FormulaContext) -> Evaluation +): IFormula = DelegateFormula( + initialState = initialState, + onInputChanged = onInputChanged, + evaluate = evaluate +) + +inline fun Formula.Companion.create( + initialState: State, + crossinline evaluate: (State, FormulaContext) -> Evaluation +): IFormula { + return create( + initialState = { _: Unit -> initialState }, + evaluate = { _, state, context -> + evaluate(state, context) + } + ) +} + +@PublishedApi +internal class DelegateFormula( + private val initialState: (Input) -> State, + private val onInputChanged: ((Input, Input, State) -> State)? = null, + private val evaluate: (Input, State, FormulaContext) -> Evaluation, + private val key: ((Input) -> Any?)? = null +): Formula { + override fun initialState(input: Input): State = initialState.invoke(input) + + override fun onInputChanged(oldInput: Input, input: Input, state: State): State { + val override = onInputChanged?.invoke(oldInput, input, state) + return override ?: super.onInputChanged(oldInput, input, state) + } + + override fun evaluate(input: Input, state: State, context: FormulaContext): Evaluation { + return evaluate.invoke(input, state, context) + } + + override fun key(input: Input): Any? { + return key?.invoke(input) ?: super.key(input) + } +} \ No newline at end of file diff --git a/formula/src/test/java/com/instacart/formula/DynamicFormulaInputTest.kt b/formula/src/test/java/com/instacart/formula/DynamicFormulaInputTest.kt index 96dfd12dc..bb7b2c830 100644 --- a/formula/src/test/java/com/instacart/formula/DynamicFormulaInputTest.kt +++ b/formula/src/test/java/com/instacart/formula/DynamicFormulaInputTest.kt @@ -9,16 +9,14 @@ class DynamicFormulaInputTest { @Test fun `using dynamic input`() { - TestFormula() + formula() .test(Observable.just(1, 2, 3)) .apply { assertThat(values()).containsExactly(1, 2, 3).inOrder() } } - class TestFormula: StatelessFormula() { - override fun evaluate(input: Int, context: FormulaContext): Evaluation { - return Evaluation(output = input) - } + private fun formula() = Formula.stateless { input: Int, context -> + Evaluation(output = input) } } diff --git a/formula/src/test/java/com/instacart/formula/FetchDataExampleTest.kt b/formula/src/test/java/com/instacart/formula/FetchDataExampleTest.kt index 37f0b2182..ae5fe4753 100644 --- a/formula/src/test/java/com/instacart/formula/FetchDataExampleTest.kt +++ b/formula/src/test/java/com/instacart/formula/FetchDataExampleTest.kt @@ -9,8 +9,7 @@ import org.junit.Test class FetchDataExampleTest { @Test fun `fake network example`() { - - MyFormula() + formula() .test() .apply { values().last().onChangeId("1") @@ -30,42 +29,34 @@ class FetchDataExampleTest { } } - class MyFormula : Formula { - private val dataRepo = DataRepo() - - data class State( - val selectedId: String? = null, - val response: DataRepo.Response? = null - ) + data class State( + val selectedId: String? = null, + val response: DataRepo.Response? = null + ) - class Output( - val title: String, - val onChangeId: (String) -> Unit - ) + class Output( + val title: String, + val onChangeId: (String) -> Unit + ) - override fun initialState(input: Unit): State = State() - - override fun evaluate( - input: Unit, - state: State, - context: FormulaContext - ): Evaluation { - return Evaluation( + private fun formula(): IFormula { + val dataRepo = DataRepo() + return Formula.create(State()) { state, context -> + Evaluation( output = Output( title = state.response?.name ?: "", onChangeId = context.eventCallback { id -> - state.copy(selectedId = id).noEffects() + transition(state.copy(selectedId = id)) } ), updates = context.updates { if (state.selectedId != null) { - events(dataRepo.fetch(state.selectedId)) { response -> - state.copy(response = response).noEffects() + dataRepo.fetch(state.selectedId).onEvent { response -> + transition(state.copy(response = response)) } } } ) } } - } diff --git a/formula/src/test/java/com/instacart/formula/InputChangedTest.kt b/formula/src/test/java/com/instacart/formula/InputChangedTest.kt index 5b8c93a49..48a225f44 100644 --- a/formula/src/test/java/com/instacart/formula/InputChangedTest.kt +++ b/formula/src/test/java/com/instacart/formula/InputChangedTest.kt @@ -6,8 +6,9 @@ import org.junit.Test class InputChangedTest { - @Test fun `input changes`() { - ParentFormula().test() + @Test + fun `input changes`() { + parentFormula().test() .output { onChildNameChanged("first") } .output { onChildNameChanged("second") } .apply { @@ -16,20 +17,16 @@ class InputChangedTest { } } - class ParentFormula : Formula { - private val childFormula = ChildFormula() + data class ParentOutput( + val childName: String, + val onChildNameChanged: (String) -> Unit + ) - data class Output(val childName: String, val onChildNameChanged: (String) -> Unit) - - override fun initialState(input: Unit): String = "default" - - override fun evaluate( - input: Unit, - state: String, - context: FormulaContext - ): Evaluation { - return Evaluation( - output = Output( + private fun parentFormula(): IFormula { + val childFormula = childFormula() + return Formula.create("default") { state, context -> + Evaluation( + output = ParentOutput( childName = context.child(childFormula, state), onChildNameChanged = context.eventCallback { name -> name.noEffects() @@ -39,20 +36,16 @@ class InputChangedTest { } } - class ChildFormula : Formula { - override fun initialState(input: String): String = input - - override fun onInputChanged(oldInput: String, input: String, state: String): String { - // We override our state with what parent provides. - return input - } - - override fun evaluate( - input: String, - state: String, - context: FormulaContext - ): Evaluation { - return Evaluation(output = state) - } + private fun childFormula(): IFormula { + return Formula.create( + initialState = { input: String -> input }, + onInputChanged = { _, new, _ -> + // We override our state with what parent provides. + new + }, + evaluate = { _, state, _ -> + Evaluation(output = state) + } + ) } } diff --git a/formula/src/test/java/com/instacart/formula/StreamFormula.kt b/formula/src/test/java/com/instacart/formula/StreamFormula.kt index 4f3a10597..765147cc9 100644 --- a/formula/src/test/java/com/instacart/formula/StreamFormula.kt +++ b/formula/src/test/java/com/instacart/formula/StreamFormula.kt @@ -9,7 +9,7 @@ class StreamFormula : Formula { val count: Int = 0 ) - class Output( + data class Output( val state: Int, val startListening: () -> Unit, val stopListening: () -> Unit diff --git a/formula/src/test/java/com/instacart/formula/tests/EmitErrorTest.kt b/formula/src/test/java/com/instacart/formula/tests/EmitErrorTest.kt index 31c5ad6ce..def095641 100644 --- a/formula/src/test/java/com/instacart/formula/tests/EmitErrorTest.kt +++ b/formula/src/test/java/com/instacart/formula/tests/EmitErrorTest.kt @@ -1,25 +1,23 @@ package com.instacart.formula.tests import com.instacart.formula.Evaluation -import com.instacart.formula.FormulaContext -import com.instacart.formula.StatelessFormula +import com.instacart.formula.Formula import com.instacart.formula.Stream +import com.instacart.formula.stateless import com.instacart.formula.test.test import java.lang.IllegalStateException object EmitErrorTest { - fun test() = MyFormula().test() + fun test() = formula().test() - class MyFormula : StatelessFormula() { - override fun evaluate(input: Unit, context: FormulaContext): Evaluation { - return Evaluation( - output = Unit, - updates = context.updates { - events(Stream.onInit()) { - throw IllegalStateException("crashed") - } + private fun formula() = Formula.stateless { context -> + Evaluation( + output = Unit, + updates = context.updates { + events(Stream.onInit()) { + throw IllegalStateException("crashed") } - ) - } + } + ) } } \ No newline at end of file