Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Multiplatform Functional Architecture with Oolong

Multiplatform Functional Architecture with Oolong

Software development is becoming increasingly functional, and for good reason. Functional programming principles like abstraction and composition enable safe, scalable, feature-driven applications. Learn how Oolong uses the Model-View-Update architectural pattern to help you write software with confidence and ease.

Michael Pardo

October 08, 2020
Tweet

More Decks by Michael Pardo

Other Decks in Programming

Transcript

  1. (λU.(λY.(λvoid.(λ0.(λsucc.(λ+.(λ*.(λ1.(λ2.(λ3.(λ4.(λ5.(λ6.(λ7.(λ8.(λ9.(λ10.(λnum.(λtrue.(λfalse.(λif.(λnot. (λand.(λor.(λmake-pair.(λpair-first.(λpair-second.(λzero?.(λpred.(λ-.(λeq?.(λ/.(λ%.(λnil.(λnil?.(λcons. (λcar.(λcdr.(λdo2.(λdo3.(λdo4.(λfor.(λprint-byte.(λprint-list.(λprint-newline.(λzero-byte.(λitoa.(λfizzmsg. (λbuzzmsg.(λfizzbuzzmsg.(λfizzbuzz.(fizzbuzz (((num 1) 0) 1)) λn.((for n)

    λi.((do2 (((if (zero? ((% i) 3))) λ_.(((if (zero? ((% i) 5))) λ_.(print-list fizzbuzzmsg)) λ_.(print-list fizzmsg))) λ_.(((if (zero? ((% i) 5))) λ_.(print-list buzzmsg)) λ_.(print-list (itoa i))))) (print-newline nil)))) ((cons (((num 0) 7) 0)) ((cons (((num 1) 0) 5)) ((cons (((num 1) 2) 2)) ((cons (((num 1) 2) 2)) ((cons (((num 0) 9) 8)) ((cons (((num 1) 1) 7)) ((cons (((num 1) 2) 2)) ((cons (((num 1) 2) 2)) nil))))))))) ((cons (((num 0) 6) 6)) ((cons (((num 1) 1) 7)) ((cons (((num 1) 2) 2)) ((cons (((num 1) 2) 2)) nil))))) ((cons (((num 0) 7) 0)) ((cons (((num 1) 0) 5)) ((cons (((num 1) 2) 2)) ((cons (((num 1) 2) 2)) nil))))) λn.(((Y λrecurse.λn.λresult.(((if (zero? n)) λ_.(((if (nil? result)) λ_.((cons zero-byte) nil)) λ_.result)) λ_. ((recurse ((/ n) 10)) ((cons ((+ zero-byte) ((% n) 10))) result)))) n) nil)) (((num 0) 4) 8)) λ_.(print- byte (((num 0) 1) 0))) (Y λrecurse.λl.(((if (nil? l)) λ_.void) λ_.((do2 (print-byte (car l))) (recurse (cdr l)))))) PRINT_BYTE) λn.λf.((((Y λrecurse.λremaining.λcurrent.λf.(((if (zero? remaining)) λ_.void) λ_.((do2 (f current)) (((recurse (pred remaining)) (succ current)) f)))) n) 0) f)) λa.do3) λa.do2) λa.λb.b) λl. (pair-second (pair-second l))) λl.(pair-first (pair-second l))) λe.λl.((make-pair true) ((make-pair e) l))) λl.(not (pair-first l))) ((make-pair false) void)) λm.λn.((- m) ((* ((/ m) n)) n))) (Y λ/.λm.λn.(((if ((eq? m) n)) λ_.1) λ_.(((if (zero? ((- m) n))) λ_.0) λ_.((+ 1) ((/ ((- m) n)) n)))))) λm.λn.((and (zero? ((- m) n))) (zero? ((- n) m)))) λm.λn.((n pred) m)) λn.(((λn.λf.λx.(pair-second ((n λp.((make-pair (f (pair-first p))) (pair-first p))) ((make-pair x) x))) n) succ) 0)) λn.((n λ_.false) true)) λp.(p false)) λp.(p true)) λx.λy.λt.((t x) y)) λa.λb.((a true) b)) λa.λb.((a b) false)) λp.λt.λf.((p f) t)) λp.λa.λb.(((p a) b) void)) λt.λf.f) λt.λf.t) λa.λb.λc.((+ ((+ ((* ((* 10) 10)) a)) ((* 10) b))) c)) (succ 9)) (succ 8)) (succ 7)) (succ 6)) (succ 5)) (succ 4)) (succ 3)) (succ 2)) (succ 1)) (succ 0)) λm.λn.λx.(m (n x))) λm.λn.λf.λx.((((m succ) n) f) x)) λn.λf.λx.(f ((n f) x))) λf.λx.x) λx.(U U)) (U λh.λf.(f λx.(((h h) f) x)))) λf.(f f))
  2. data class Model( val count: Int ) sealed class Msg

    { object Increment : Msg() object Decrement : Msg() }
  3. data class Model( val count: Int ) sealed class Msg

    { object Increment : Msg() object Decrement : Msg() } data class Props( )
  4. data class Model( val count: Int ) sealed class Msg

    { object Increment : Msg() object Decrement : Msg() } data class Props( val count: Int, )
  5. data class Model( val count: Int ) sealed class Msg

    { object Increment : Msg() object Decrement : Msg() } data class Props( val count: Int, val increment: (Dispatch<Msg>) -> Unit, )
  6. data class Model( val count: Int ) sealed class Msg

    { object Increment : Msg() object Decrement : Msg() } data class Props( val count: Int, val increment: (Dispatch<Msg>) -> Unit, val decrement: (Dispatch<Msg>) -> Unit, )
  7. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> } }
  8. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> model.copy(count = model.count + 1) } }
  9. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> model.copy(count = model.count + 1) Decrement -> } }
  10. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } }
  11. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() }
  12. val view: (Model) -> Props = { model -> Props(

    count = model.count, increment = { dispatch -> dispatch(Increment) }, ) }
  13. val view: (Model) -> Props = { model -> Props(

    count = model.count, increment = { dispatch -> dispatch(Increment) }, decrement = { dispatch -> dispatch(Decrement) }, ) }
  14. val render: (Props, Dispatch<Msg>) -> Any? = { props, dispatch

    -> setContent { Counter(props, dispatch) } }
  15. @Composable fun Counter(props: Props, dispatch: Dispatch<Msg>) { OutlinedButton(onClick = {

    props.decrement(dispatch) }) { Icon(vectorResource(R.drawable.ic_decrement)) } }
  16. @Composable fun Counter(props: Props, dispatch: Dispatch<Msg>) { OutlinedButton(onClick = {

    props.decrement(dispatch) }) { Icon(vectorResource(R.drawable.ic_decrement)) } Text("") }
  17. @Composable fun Counter(props: Props, dispatch: Dispatch<Msg>) { OutlinedButton(onClick = {

    props.decrement(dispatch) }) { Icon(vectorResource(R.drawable.ic_decrement)) } Text("${props.count}") }
  18. @Composable fun Counter(props: Props, dispatch: Dispatch<Msg>) { OutlinedButton(onClick = {

    props.decrement(dispatch) }) { Icon(vectorResource(R.drawable.ic_decrement)) } Text("${props.count}") Button(onClick = { }) { Icon(vectorResource(R.drawable.ic_increment)) } }
  19. @Composable fun Counter(props: Props, dispatch: Dispatch<Msg>) { OutlinedButton(onClick = {

    props.decrement(dispatch) }) { Icon(vectorResource(R.drawable.ic_decrement)) } Text("${props.count}") Button(onClick = { props.increment(dispatch) }) { Icon(vectorResource(R.drawable.ic_increment)) } }
  20. @Test fun `initial Model count should be 0`() { val

    expected = Model(count = 0) val (actual, _) = init() }
  21. @Test fun `initial Model count should be 0`() { val

    expected = Model(count = 0) val (actual, _) = init() assertEquals(expected, actual) }
  22. @Test fun `Increment msg should increment Model count`() { val

    msg = Msg.Increment val model = Model(count = 0) val expected = Model(count = 1) }
  23. @Test fun `Increment msg should increment Model count`() { val

    msg = Msg.Increment val model = Model(count = 0) val expected = Model(count = 1) val (actual, _) = update(msg, model) }
  24. @Test fun `Increment msg should increment Model count`() { val

    msg = Msg.Increment val model = Model(count = 0) val expected = Model(count = 1) val (actual, _) = update(msg, model) assertEquals(expected, actual) }
  25. @Test fun `Decrement msg should increment Model count`() { val

    msg = Msg.Decrement val model = Model(count = 0) val expected = Model(count = -1) }
  26. @Test fun `Decrement msg should increment Model count`() { val

    msg = Msg.Decrement val model = Model(count = 0) val expected = Model(count = -1) val (actual, _) = update(msg, model) }
  27. @Test fun `Decrement msg should increment Model count`() { val

    msg = Msg.Decrement val model = Model(count = 0) val expected = Model(count = -1) val (actual, _) = update(msg, model) assertEquals(expected, actual) }
  28. @Test fun `Props count should be equal to Model count`()

    { val model = Model(count = 0) val expected = model.count }
  29. @Test fun `Props count should be equal to Model count`()

    { val model = Model(count = 0) val expected = model.count val props = view(model) val actual = props.count }
  30. @Test fun `Props count should be equal to Model count`()

    { val model = Model(count = 0) val expected = model.count val props = view(model) val actual = props.count assertEquals(expected, actual) }
  31. @Test fun `Props increment should dispatch Increment msg`() { val

    model = Model(count = 0) val props = view(model) }
  32. @Test fun `Props increment should dispatch Increment msg`() { val

    model = Model(count = 0) val props = view(model) props.increment { msg -> } }
  33. @Test fun `Props increment should dispatch Increment msg`() { val

    model = Model(count = 0) val props = view(model) props.increment { msg -> assertEquals(msg, Msg.Increment) } }
  34. @Test fun `Props decrement should dispatch Decrement msg`() { val

    model = Model(count = 0) val props = view(model) }
  35. @Test fun `Props decrement should dispatch Decrement msg`() { val

    model = Model(count = 0) val props = view(model) props.decrement { msg -> } }
  36. @Test fun `Props decrement should dispatch Decrement msg`() { val

    model = Model(count = 0) val props = view(model) props.decrement { msg -> assertEquals(msg, Msg.Decrement) } }
  37. class CounterService(context: Context) { private val prefs = context.getSharedPreferences("counter", MODE_PRIVATE)

    val getCount: GetCount = { prefs.getInt("count", 0) } val putCount: PutCount = { count -> } }
  38. class CounterService(context: Context) { private val prefs = context.getSharedPreferences("counter", MODE_PRIVATE)

    val getCount: GetCount = { prefs.getInt("count", 0) } val putCount: PutCount = { count -> prefs.edit().putInt("count", count).apply() } }
  39. { } { count -> } // typealias GetCount =

    () -> Int // typealias PutCount = (Int) -> Unit val getCount: GetCount = val putCount: PutCount =
  40. private val getCountEffect: (GetCount) -> Effect<Msg> = { getCount ->

    effect { dispatch -> val count = getCount() } }.
  41. private val getCountEffect: (GetCount) -> Effect<Msg> = { getCount ->

    effect { dispatch -> val count = getCount() dispatch(Msg.SetCount(count)) } }.
  42. val init: (GetCount) -> () -> Pair<Model, Effect<Msg>> = {.getCount

    -> { Model(count = 0) to getCountEffect(getCount) } }
  43. sealed class Msg { class SetCount(val count: Int) : Msg()

    object Increment : Msg() object Decrement : Msg() }
  44. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() }
  45. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { is SetCount -> Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() }
  46. val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = { msg,

    model -> when (msg) { is SetCount -> model.copy(count = msg.count). Increment -> model.copy(count = model.count + 1). Decrement -> model.copy(count = model.count - 1). } to none() }
  47. val update: (PutCount) -> (Msg, Model) -> Pair<Model, Effect<Msg>> =

    { putCount -> { msg, model -> when (msg) { is SetCount -> model.copy(count = msg.count) Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() } }
  48. val update: (PutCount) -> (Msg, Model) -> Pair<Model, Effect<Msg>> =

    { putCount -> val putCountEffect = putCountEffect(putCount) { msg, model -> when (msg) { is SetCount -> model.copy(count = msg.count) Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() } }
  49. val update: (PutCount) -> (Msg, Model) -> Pair<Model, Effect<Msg>> =

    { putCount -> val putCountEffect = putCountEffect(putCount) { msg, model -> when (msg) { is SetCount -> model.copy(count = msg.count) Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to none() } }
  50. val update: (PutCount) -> (Msg, Model) -> Pair<Model, Effect<Msg>> =

    { putCount -> val putCountEffect = putCountEffect(putCount) { msg, model -> when (msg) { is SetCount -> model.copy(count = msg.count) Increment -> model.copy(count = model.count + 1) Decrement -> model.copy(count = model.count - 1) } to putCountEffect(model.count) } }
  51. @Test fun `initial Effect should dispatch SetCount`() { val getCount:

    GetCount = { 0 } val expected = Msg.SetCount(getCount()) val (_, effect) = init() }
  52. @Test fun `initial Effect should dispatch SetCount`() { val getCount:

    GetCount = { 0 } val expected = Msg.SetCount(getCount()) val (_, effect) = init() runBlocking { effect { actual -> } } }
  53. @Test fun `initial Effect should dispatch SetCount`() { val getCount:

    GetCount = { 0 } val expected = Msg.SetCount(getCount()) val (_, effect) = init() runBlocking { effect { actual -> assertEquals(expected, actual) } } }
  54. @Test fun `update effect should put count`() { val msg

    = Msg.SetCount(count = 42) val model = Model(count = 0) val putCount: PutCount = { count -> } }
  55. @Test fun `update effect should put count`() { val msg

    = Msg.SetCount(count = 42) val model = Model(count = 0) val putCount: PutCount = { count -> } val (_, effect) = update(putCount)(msg, model) runBlocking { effect { } } }
  56. @Test fun `update effect should put count`() { val msg

    = Msg.SetCount(count = 42) val model = Model(count = 0) val putCount: PutCount = { count -> assertEquals(count, msg.count) } val (_, effect) = update(putCount)(msg, model) runBlocking { effect { } } }
  57. object Child { class Model class Msg class Props val

    init: () -> Pair<Model, Effect<Msg>> = ... }
  58. object Child { class Model class Msg class Props val

    init: () -> Pair<Model, Effect<Msg>> = ... val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = ... }
  59. object Child { class Model class Msg class Props val

    init: () -> Pair<Model, Effect<Msg>> = ... val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = ... val view: (Model) -> Props = ... }
  60. object Child { class Model class Msg class Props val

    init: () -> Pair<Model, Effect<Msg>> = ... val update: (Msg, Model) -> Pair<Model, Effect<Msg>> = ... val view: (Model) -> Props = ... }
  61. object Parent { class.Model class Msg class Props val init:

    () -> Pair<Model, Effect<Msg>> = ... val update: (Msg, Model).-> Pair<Model, Effect<Msg>> = ... val view: (Model).-> Props = ... }
  62. sealed class Msg { class SetCounts(val counts: Map<Id, Int>) :

    Msg() object AddCounter : Msg() class RemoveCounter(val id: Id) : Msg() class CounterMsg( val id: Id, val msg: Counter.Msg ) : Msg() }
  63. class Props( val counters: List<CounterRow>, val addCounter: (Dispatch<Msg>) -> Unit,

    ) { class CounterRow( val props: Counter.Props, val removeCounter: (Dispatch<Msg>) -> Unit, ) }