Skip to content
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

[core] exploring functional formula creation. #170

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions formula/src/main/java/com/instacart/formula/Formula.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ interface Formula<Input, State : Any, Output> : IFormula<Input, Output> {
override fun implementation(): Formula<Input, *, Output> {
return this
}

companion object {
// Used to attach extension functions.
}
}
64 changes: 64 additions & 0 deletions formula/src/main/java/com/instacart/formula/FormulaExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.instacart.formula

inline fun <Input, Output> Formula.Companion.stateless(
crossinline output: (Input, FormulaContext<Unit>) -> Evaluation<Output>
): IFormula<Input, Output> = DelegateFormula<Input, Unit, Output>(
initialState = { Unit },
evaluate = { input, _, context ->
output(input, context)
}
)

inline fun <Output> Formula.Companion.stateless(
crossinline output: (FormulaContext<Unit>) -> Evaluation<Output>
): IFormula<Unit, Output> = DelegateFormula<Unit, Unit, Output>(
initialState = { Unit },
evaluate = { input, state, context ->
output(context)
}
)

fun <Input, State: Any, Output> Formula.Companion.create(
initialState: (Input) -> State,
onInputChanged: ((Input, Input, State) -> State)? = null,
evaluate: (Input, State, FormulaContext<State>) -> Evaluation<Output>
): IFormula<Input, Output> = DelegateFormula(
initialState = initialState,
onInputChanged = onInputChanged,
evaluate = evaluate
)

inline fun <State: Any, Output> Formula.Companion.create(
initialState: State,
crossinline evaluate: (State, FormulaContext<State>) -> Evaluation<Output>
): IFormula<Unit, Output> {
return create(
initialState = { _: Unit -> initialState },
evaluate = { _, state, context ->
evaluate(state, context)
}
)
}

@PublishedApi
internal class DelegateFormula<Input, State: Any, Output>(
private val initialState: (Input) -> State,
private val onInputChanged: ((Input, Input, State) -> State)? = null,
private val evaluate: (Input, State, FormulaContext<State>) -> Evaluation<Output>,
private val key: ((Input) -> Any?)? = null
): Formula<Input, State, Output> {
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<State>): Evaluation<Output> {
return evaluate.invoke(input, state, context)
}

override fun key(input: Input): Any? {
return key?.invoke(input) ?: super.key(input)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int, Int>() {
override fun evaluate(input: Int, context: FormulaContext<Unit>): Evaluation<Int> {
return Evaluation(output = input)
}
private fun formula() = Formula.stateless { input: Int, context ->
Evaluation(output = input)
}
}
41 changes: 16 additions & 25 deletions formula/src/test/java/com/instacart/formula/FetchDataExampleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import org.junit.Test
class FetchDataExampleTest {

@Test fun `fake network example`() {

MyFormula()
formula()
.test()
.apply {
values().last().onChangeId("1")
Expand All @@ -30,42 +29,34 @@ class FetchDataExampleTest {
}
}

class MyFormula : Formula<Unit, MyFormula.State, MyFormula.Output> {
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<State>
): Evaluation<Output> {
return Evaluation(
private fun formula(): IFormula<Unit, Output> {
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))
}
}
}
)
}
}

}
53 changes: 23 additions & 30 deletions formula/src/test/java/com/instacart/formula/InputChangedTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -16,20 +17,16 @@ class InputChangedTest {
}
}

class ParentFormula : Formula<Unit, String, ParentFormula.Output> {
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<String>
): Evaluation<Output> {
return Evaluation(
output = Output(
private fun parentFormula(): IFormula<Unit, ParentOutput> {
val childFormula = childFormula()
return Formula.create("default") { state, context ->
Evaluation(
output = ParentOutput(
childName = context.child(childFormula, state),
onChildNameChanged = context.eventCallback { name ->
name.noEffects()
Expand All @@ -39,20 +36,16 @@ class InputChangedTest {
}
}

class ChildFormula : Formula<String, String, String> {
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<String>
): Evaluation<String> {
return Evaluation(output = state)
}
private fun childFormula(): IFormula<String, String> {
return Formula.create(
initialState = { input: String -> input },
onInputChanged = { _, new, _ ->
// We override our state with what parent provides.
new
},
evaluate = { _, state, _ ->
Evaluation(output = state)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class StreamFormula : Formula<Unit, StreamFormula.State, StreamFormula.Output> {
val count: Int = 0
)

class Output(
data class Output(
val state: Int,
val startListening: () -> Unit,
val stopListening: () -> Unit
Expand Down
24 changes: 11 additions & 13 deletions formula/src/test/java/com/instacart/formula/tests/EmitErrorTest.kt
Original file line number Diff line number Diff line change
@@ -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<Unit, Unit>() {
override fun evaluate(input: Unit, context: FormulaContext<Unit>): Evaluation<Unit> {
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")
}
)
}
}
)
}
}