$30 off During Our Annual Pro Sale. View Details »

Swift アクターモデルと Elm Architecture の融合 / iOSDC Japan 2022

Yasuhiro Inami
September 11, 2022

Swift アクターモデルと Elm Architecture の融合 / iOSDC Japan 2022

Swift アクターモデルと Elm Architecture の融合 by 稲見 泰宏 | トーク | iOSDC Japan 2022 - fortee.jp
https://fortee.jp/iosdc-japan-2022/proposal/aa8ecff3-682b-4127-93ac-321b9ec4cf30

Yasuhiro Inami

September 11, 2022
Tweet

More Decks by Yasuhiro Inami

Other Decks in Programming

Transcript

  1. Swift ΞΫλʔϞσϧͱ Elm Architecture ͷ༥߹ 2022/09/11 iOSDC Japan 2022 Yasuhiro

    Inami / @inamiy
  2. ΞδΣϯμ  ΞΫλʔϞσϧͱ&MN"SDIJUFDUVSFͷઆ໌  "DUPNBUPOͷ঺հ  "DUPNBUPOʹ͓͚Δʮঢ়ଶʯͱʮ෭࡞༻ʯͷ؅ཧ  "DUPNBUPOΛ࢖ͬͨΞϓϦͷઃܭࢦ਑ 

    ΞΫλʔϞσϧ ΦϒδΣΫτࢦ޲ϓϩάϥϛϯά ͱ 
 &MN"SDIJUFDUVSF ؔ਺ܕϓϩάϥϛϯά ͷ༥߹
  3. ΞΫλʔϞσϧ

  4. None
  5. Swift Concurrency 
 1. async /await 2. Structured Concurrency 3.

    Actors
  6. None
  7. actor Counter { var count: Int = 0 // ಺෦ঢ়ଶ

    func increment() { // τϥϯβΫγϣϯ count += 1 // ঢ়ଶߋ৽ print(count) // ෭࡞༻ͷ࣮ߦ } } // ΞΫλʔͷ࢖༻ྫ let counter = Counter() // ΞΫλʔ֎෦͔ΒΞΫλʔʹΞΫηε͢Δ৔߹ɺ async / await ඇಉظॲཧʹͳΔ await counter.increment() ΞΫλʔͷྫ ΞΫλʔঢ়ଶͱͦͷૢ࡞ΛҰՕॴʹ·ͱΊͨ ʮεϨου҆શʯͳΦϒδΣΫτ ΞΫλʔִ཭͞Εͨϝιου εϨου҆શΛอূ͢Δ τϥϯβΫγϣϯ
  8. protocol CounterProtocol: Actor { func increment() // τϥϯβΫγϣϯͷந৅ } actor

    Counter { var count: Int = 0 // ಺෦ঢ়ଶ } extension Counter: CounterProtocol { func increment() { // τϥϯβΫγϣϯͷ࣮૷ count += 1 // ঢ়ଶߋ৽ print(count) // ෭࡞༻ͷ࣮ߦ } } ΞΫλʔͷྫ 1SPUPDPMʹΑΔந৅Խ  QSPUPDPMʹΑΔ 
 ۩ମΞΫλʔͷ ϝιουͷந৅Խ QSPUPDPM४ڌʹΑΔ 
 ந৅ϝιουͷ࣮૷
  9. Elm Architecture

  10. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect
  11. enum Action: Sendable { case increment } struct State: Sendable,

    Equatable { var count: Int = 0 } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } } &MN"SDIJUFDUVSFͷྫ 4XJGU຋༁ ܭࢉϩδοΫͷೖྗ 
 ΞΫγϣϯ ΛFOVNͰදݱ ܭࢉϩδοΫ 3FEVDFS  ʮ"DUJPOʯͱʮݱࡏͷঢ়ଶʯΛ 
 Ҿ਺Ͱड͚औΓɺʮ৽͍͠ঢ়ଶʯͱ 
 ʮ෭࡞༻ʯΛฦؔ͢਺ ෭࡞༻ QSJOU΍BTZOD Λ & ff FDUͷதͰ࣮ߦ͢Δ
  12. enum Action: Sendable { case increment } struct State: Sendable,

    Equatable { var count: Int = 0 } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } } &MN"SDIJUFDUVSFͷྫ 4XJGU຋༁ Elm Architecture Ͱ͸ class / actor ͕ొ৔͠ͳ͍ (ΑΓ୯७ͳ enum, struct, func ͕ओ໾)
  13. // Elm Architecture enum Action: Sendable { case increment }

    struct State: Sendable, Equatable { var count: Int = 0 } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } } &MN"SDIJUFDUVSFͱΞΫλʔϞσϧͷൺֱ // ΞΫλʔϞσϧ protocol CounterProtocol: Actor { func increment() // τϥϯβΫγϣϯͷந৅ } actor Counter { var count: Int = 0 // ಺෦ঢ়ଶ } extension Counter: CounterProtocol { func increment() { // τϥϯβΫγϣϯͷ࣮૷ count += 1 // ঢ়ଶߋ৽ print(count) // ෭࡞༻ͷ࣮ߦ } }
  14. enum Action { case increment } struct State { var

    count: Int = 0 } let reducer = Reducer { action, state in switch action { case .increment: state.count += 1 return Effect { print(state.count) 
 } } } &MN"SDIJUFDUVSFͷྫ 4XJGU຋༁ protocol CounterProtocol: Actor { func increment() } actor Counter { var count: Int = 0 } extension Counter: CounterProtocol { func increment() { count += 1 print(count) } } Actor = ΦϒδΣΫτࢦ޲ Elm Architecture = ؔ਺ܕ ྆ऀʹີ઀ͳؔ܎͕͋Δʂ
  15. ΞΫλʔϞσϧͱ&MN"SDIJUFDUVSF wΞΫλʔϞσϧ ΦϒδΣΫτࢦ޲ϓϩάϥϛϯά  w௚ྻॲཧͷ&YFDVUPSΛ࣋ͭɺίϯύΠϧ࣌εϨου҆શ ੑ͕อূ͞ΕͨΦϒδΣΫτ wΦϒδΣΫτ܈ͷ෼ࢄ؅ཧʹڧ͍ʢଟਆڭʣ w&MN"SDIJUFDUVSF ؔ਺ܕϓϩάϥϛϯά 

    wΞΫγϣϯɺঢ়ଶɺ3FEVDFSͷ୯७σʔλߏ଄Λ߹੒ w4JOHMF4PVSDFPG5SVUIΛߏ੒Ͱ͖ΔʢҰਆڭʣ
  16. ࠷ۙͷ4XJGUJ04։ൃͷτϨϯυ  .77. 7JFX.PEFM ʹΞΫλʔϞσϧΛద༻ w !.BJO"DUPSʹΑΔϝΠϯεϨου҆શੑ  $PNQPTBCMF"SDIJUFDUVSF 5$"

     &MNελΠϧ  w ঢ়ଶͷू໿؅ཧʹΑΔςετɾσόοΪϯά 🤔 2྆ํͷྑ͍ॴऔΓ͕Ͱ͖ͳ͍΋ͷ͔ʜʁ
  17. Actomaton 
 http://github.com/Actomaton/Actomaton

  18. J04%$+BQBO 3FBDUJWF4UBUF.BDIJOF IUUQTTQFBLFSEFDLDPNJOBNJZSFBDUJWFTUBUF NBDIJOFKBQBOFTF J04%$+BQBO 
 4XJGU6*࣌୅ͷ 
 'VODUJPOBMJ04"SDIJUFDUVSF IUUQTTQFBLFSEFDLDPNJOBNJZJPTED

    KBQBO
  19. "DUPNBUPOͱ͸ʁʢ7FSݱࡏʣ w4XJGU$PODVSSFODZͱΞΫλʔϞσϧΛ࢖ͬͨ&MN"SDIJUFDUVSF w७ਮͳΞΫλʔ7JFX.PEFMʹൺ΂ͯɺΑΓ҆શʹঢ়ଶͱ෭࡞༻Λ؅ཧ wJ04%$+BQBOͰ঺հͨ͠)BSWFTU $PNCJOF Λ4XJGU $PODVSSFODZʹஔ͖׵͑ͨ࠷৽ܗ w%FQFOEFODZ*OKFDUJPOίϯςφΛ݉ͶΔ ςετͱσόοΪϯά 

    w4XJGU6*6*,JU-JOVY؀ڥͰ࢖͑ɺ6*ϑϨʔϜϫʔΫ༗ແΛ໰Θͳ͍ w਺ઍສϢʔβʔن໛ͷJ04ΞϓϦͰಋೖ࣮੷
  20. Demo 
 http://github.com/Actomaton/Actomaton-Gallery

  21. None
  22. None
  23. None
  24. None
  25. None
  26. ࢀߟɿ4XJGU6*ͰඥΛදݱ͢Δ IUUQT[FOOEFWJLFIBSUJDMFTBGFGEBCG IUUQTUXJUUFSDPN%-9TUBUVT

  27. None
  28. None
  29. "DUPNBUPO(BMMFSZ σϞΞϓϦ ͷಛ௃ w࠶ར༻ੑͷߴ͍ϚΠΫϩϞδϡʔϧߏ੒ w4XJGU6*6*,JU .77.Ͱ͸࣮ݱ͕೉͍͠ػೳΛ࣮૷Ͱ͖Δ w೚ҙͷঢ়ଶ͔ΒͷΞϓϦىಈ σΟʔϓϦϯΫɺ։ൃαΠΫϧߴ଎Խ  wঢ়ଶࠩ෼ͷσόοάϩά

    wλΠϜτϥϕϧʹΑΔ6OEP3FEP w෭࡞༻ͷΩϡʔ؅ཧ & ff FDU2VFVF &MN$PNQPTBCMF"SDIJUFDUVSF 
 ʹ͸උΘ͍ͬͯͳ͍ಠࣗػೳ
  30. Actomaton ͷ࢖͍ํ

  31. enum Action: Sendable { case increment } struct State: Sendable,

    Equatable { var count: Int = 0 } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect.fireAndForget { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } } ঢ়ଶ؅ཧͷίʔυྫ ϩδοΫ 3FEVDFSͷجຊܗɿ ύλʔϯϚον ঢ়ଶͷόϦσʔγϣϯͱߋ৽ ෭࡞༻ͷొ࿥ 
 ʢඇಉظܭࢉޙͷ࣍ͷΞΫγϣϯ ΋ࢦఆՄೳʣ Ϋϩʔδϟʹแ·Εͨ෭࡞༻͸ʮ࣮ߦલʯ 
 ͳͷͰ3FEVDFS͸ʮ७ਮؔ਺ʯ
  32. enum Action: Sendable { // case ͕૿͑ͨ৔߹ case increment case

    decrementIfPositive } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect.fireAndForget { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } case .decrementIfPositive: // ঢ়ଶͷόϦσʔγϣϯ guard state.count > 0 else { return Effect.empty } state.count -= 1 // ঢ়ଶߋ৽ return Effect.fireAndForget { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } }
  33. enum Action: Sendable { // case ͕૿͑ͨ৔߹ case increment case

    decrementIfPositive } let reducer = Reducer { action, state, _ in switch action { // ΞΫγϣϯͷύλʔϯϚον case .increment: state.count += 1 // ঢ়ଶߋ৽ return Effect.fireAndForget { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } case .decrementIfPositive: // ঢ়ଶͷόϦσʔγϣϯ guard state.count > 0 else { return Effect.empty } state.count -= 1 // ঢ়ଶߋ৽ return Effect.fireAndForget { [state] in print(state.count) // ෭࡞༻ͷ࣮ߦ } } } ύλʔϯϚον ঢ়ଶͷόϦσʔγϣϯͱߋ৽ ෭࡞༻ͷొ࿥ ৗʹಉ͡εςοϓͰهड़͢ΔͨΊɺ ঢ়ଶͷมߋՕॴ͕෼͔Γ΍͍͢ "DUPS3FFOUSBODZ໰୊ͷ ݕ஌͕͠΍͍͢  ·ͨɺ3FEVDFS͸TFMG BDUPS Λ ؚ·ͳ͍ͷͰɺෆ஫ҙʹΑΔTFMG॥ ؀ࢀর͕ى͖ͳ͍҆શઃܭʹͳΔ
  34. let store = Store<Action, State, Void>( state: State(), // ॳظঢ়ଶ

    reducer: reducer ) struct ContentView: View { let store: Store<Action, State, Void> var body: some View { WithViewStore(store) { viewStore in Button("Count: \(viewStore.count)") { store.send(.increment) } } } } ঢ়ଶ؅ཧͷίʔυྫ 4UPSFԽͱ4XJGU6*ద༻ ΞΫγϣϯɺঢ়ଶɺ3FEVDFSΛ ଋͶͨ4UPSF .BJO"DUPS Λ࡞੒ 4UPSFΛ7JFX4UPSF 
 4XJGU6*!0CTFSWFE0CKFDU 
 ʹม׵ͯ͠࢖༻
  35. Reducer State Action CounterView Store 4UPSFΛ࡞Γɺ 7JFXʹ࣋ͨͤΔ (CounterLogic) 4UPSF ঢ়ଶ؅ཧΦϒδΣΫτ

     Λߏ੒͢ΔͨΊͷܕͱϩδοΫ ؔ਺ܕελΠϧ (߹੒͠΍͍͢) ΦϒδΣΫτࢦ޲ (߹੒͠ʹ͍͘) Reducer State Action
  36. ґଘ஫ೖͷίʔυྫ enum Action: Sendable { case fetch case _didFetch(Response) }

    struct State: Sendable, Equatable { var isLoading: Bool = false } struct Environment: Sendable { var fetch: @Sendable (Request) async throws -> Response } &OWJSPONFOU ґଘΛ஫ೖ͢Δܕ
  37. ґଘ஫ೖͷίʔυྫ let reducer = Reducer { action, state, environment in

    switch action { case .fetch: state.isLoading = true return Effect { let response = try await environment.fetch(Request(...)) return Action._didFetch(response) 
 } case let ._didFetch(response): state.isLoading = false return Effect.fireAndForget { print(response) } } } &OWJSPONFOUΛ࢖ͬͯ ෭࡞༻Λ࣮ߦ
  38. &GGFDU*%ʹΑΔ෭࡞༻ͷΩϟϯηϧ struct FetchEffectID: EffectIDProtocol {} let reducer = Reducer {

    action, state, environment in switch action { case .fetch: state.isLoading = true return Effect.cancel(id: FetchEffectID()) + Effect(id: FetchEffectID()) { let response = try await environment.fetch(Request(...)) return Action._didFetch(response) 
 } case let ._didFetch(response): state.isLoading = false return Effect.fireAndForget { print(response) } } } & ff FDU*%Λఆٛ & ff FDUʹ 
 & ff FDU*%Λઃఆ͠ɺ ΩϟϯηϧՄೳʹ͢Δ
  39. &GGFDU2VFVFʹΑΔ෭࡞༻؅ཧ struct FetchEffectQueue: Newest1EffectQueueProtocol {} let reducer = Reducer {

    action, state, environment in switch action { case .fetch: state.isLoading = true return Effect(queue: FetchEffectQueue()) { let response = try await environment.fetch(Request(...)) return Action._didFetch(response) 
 } case let ._didFetch(response): state.isLoading = false return Effect.fireAndForget { print(response) } } } & ff FDU2VFVFΛఆٛ /FXFTU͸࠷৽݅ͷΈ࣮ߦ & ff FDUʹ 
 & ff FDU2VFVFΛઃఆ͠ɺ ෳ਺ͷ෭࡞༻ΛΩϡʔͰ؅ཧ͢Δ
  40. &GGFDU2VFVFͷछྨ protocol EffectQueueProtocol: Hashable, Sendable { /// Runs newest or

    oldest with maxCount. var effectQueuePolicy: EffectQueuePolicy { get } /// Effect starting delay. var effectQueueDelay: EffectQueueDelay { get } } // Rx flatMapLatest / switchLatest protocol Newest1EffectQueueProtocol: EffectQueueProtocol { ... } // Rx flatMapFirst (or RxAction) protocol Oldest1DiscardNewEffectQueueProtocol: EffectQueueProtocol { ... } // Rx flatMapConcat protocol Oldest1SuspendNewEffectQueueProtocol: EffectQueueProtocol { ... }
  41. %FGBVMU .FSHF 0MEFTU4VTQFOE/FX /FXFTU 0MEFTU%JTDBSE/FX

  42. EffectQueue (Newest1) EffectQueue (Oldest1DiscardNew) EffectQueue (Oldest1SuspendNew) Store Reducer State Effect

    Action View Feedback Action Running Effects Environment
  43. Actomaton Λ࢖ͬͨ 
 ΞϓϦͷઃܭࢦ਑

  44. "DUPNBUPOΛ࢖ͬͨΞϓϦͷઃܭࢦ਑  ؔ਺ܕϓϩάϥϛϯάํࣜ 4JOHMF4PVSDFPG5SVUI  w"DUJPO 4UBUF 3FEVDFSͷ߹੒ͱ4UPSFͷ෼ղɾ࠷దԽ w௕ॴɿঢ়ଶͷू໿؅ཧɺ୹ॴɿ߹੒ͱ෼ղͷ೉қ౓͕ߴ͍ 

    ΦϒδΣΫτࢦ޲ํࣜ .VMUJ7JFX.PEFMT  w4UPSFΛ7JFX.PEFMͱಉҰࢹ͠ɺ߹੒ͱ෼ղΛߦΘͳ͍ w௕ॴɿ୭Ͱ΋ཧղͰ͖Δɺ୹ॴɿঢ়ଶͷ෼ࢄ؅ཧ ΞϓϦશମͰ 4UPSF͕ͭ ֤ը໘͝ͱʹ 4UPSFΛ༻ҙ
  45. ؔ਺ܕϓϩάϥϛϯάํࣜ 
 (Single Source of Truth)

  46. Reducer State Action CounterView Store 4UPSFΛ࡞Γɺ 7JFXʹ࣋ͨͤΔ (CounterLogic) 4UPSF ঢ়ଶ؅ཧΦϒδΣΫτ

     Λߏ੒͢ΔͨΊͷܕͱϩδοΫ ؔ਺ܕελΠϧ (߹੒͠΍͍͢) ΦϒδΣΫτࢦ޲ (߹੒͠ʹ͍͘) Reducer State Action
  47. Reducer State Action (CounterLogic) Reducer State Action (DownloaderLogic) Reducer State

    Action (PhysicsLogic) Environment Environment Environment ֤αϯϓϧը໘ͷϩδοΫ
  48. (AppLogic) Environment Counter Environment Downloader Environment Physics Environment Reducer Counter

    Reducer Downloader Reducer Physics Reducer State Counter State Downloader State Physics State Action Counter Action Downloader Action Physics Action "DUJPO 4UBUF 3FEVDFS &OWJSPONFOU୯ҐͰ߹੒
  49. Reducer State Action AppView Store (AppLogic) ΞϓϦͷશͯΛ໢ཏ͢Δ 
 ܕͱϩδοΫ Reducer

    State Action Environment Environment 4JOHMF4PVSDF PG5SVUIͳ །Ұਆͷ஀ੜ
  50. CounterView (Computed) Store Counter Reducer Counter State Counter Action Counter

    Environment AppView Store App Reducer App State App Action App Environment ֤ը໘͸ ΞϓϦશମͷ ৘ใΛ஌Βͳ͍ ૄ݁߹ ࢠཁૉʹม׵ 4JOHMF 4PVSDF PG5SVUI 4UPSF෼ղ ೿ੜ4UPSF ݩͷ4UPSF͔Β ࢉग़ͨ͠΋ͷ
  51. CounterView (Child)Store Counter Reducer Counter State Counter Action Counter Environment

    AppView Store App Reducer App State App Action App Environment ֤ը໘͸ ΞϓϦશମͷ ৘ใΛ஌Βͳ͍ ࢠཁૉʹม׵ 4JOHMF 4PVSDF PG5SVUI 4UPSF෼ղ ΦϒδΣΫτ߹੒ͷ୅ΘΓʹ ؔ਺ (ܕͱReducer) Λ߹੒ Single Source of Truth ͳ 
 ΦϒδΣΫτΛ֤ը໘ʹ෼ղ͢Δ
  52. // App.Action enum Action: Sendable { case changeCurrent(State.Current?) // NavigationView

    ੾Γସ͑࣌ʹൃՐ case counter(Counter.Action) case gameOfLife(GameOfLife.Root.Action) case physics(PhysicsRoot.Action) case downloader(Downloader.Action) ... } // App.State struct State: Equatable, Sendable { var current: Current? // ݱࡏදࣔதͷը໘ʢnil ͷ৔߹ɺHome ը໘ʣ enum Current: Equatable, Sendable { case counter(Counter.State) case gameOfLife(GameOfLife.Root.State) case physics(PhysicsRoot.State) case downloader(Downloader.State) ... } } ֤ը໘ͷΞΫγϣϯΛ શମͷΞΫγϣϯʹ߹੒ ֤ը໘ͷঢ়ଶΛ શମͷঢ়ଶʹ߹੒ 
 /05&֤ը໘ͷঢ়ଶ͸ 
 ಉ࣌ʹଘࡏ͠ͳ͍ͷͰ FOVNͰఆٛ͢Δ
  53. let appReducer = Reducer<Action, State, Environment>.combine( Counter.reducer .contramap(action: /Action.counter) .contramap(state:

    /State.Current.counter) .contramap(state: \State.current) .contramap(environment: { _ in () }), GameOfLife.Root.reducer() .contramap(action: /Action.gameOfLife) .contramap(state: /State.Current.gameOfLife) .contramap(state: \State.current) .contramap(environment: { $0.gameOfLife }), PhysicsRoot.reducer .contramap(action: /Action.physics) .contramap(state: /State.Current.physics) .contramap(state: \State.current) .contramap(environment: { .init(timer: $0.timer) }), Downloader.reducer .contramap(action: /Action.downloader) .contramap(state: /State.Current.downloader) .contramap(state: \State.current) .contramap(environment: \.downloader), ... // ҎԼɺ߹੒͍ͨ͠ը໘ͷ਺͚ͩม׵ίʔυΛॻ͘ ) ֤ը໘ͷ3FEVDFSΛ ࢠը໘͔Β਌ը໘ʹ޲͚ͯ ม׵ܭࢉͯ݁͠߹͢Δ 5$"ͷQVMMCBDL  ؔ਺ܕϓϩάϥϛϯάʹ͓͚Δ 
 ൓มؔख DPOUSBNBQ  -FOT 1SJTNΛ࢖͍ͬͯΔ J04%$ϓϨθϯࢀর
  54. @MainActor struct AppView: View { let store: Store<Action, State, Environment>

    var body: some View { NavigationView { WithViewStore(store.map(state: \.common)) { viewStore in List(exampleList, id: \.exampleTitle) { example in navigationLink(example: example) } } } } func navigationLink(example: Example) -> some View { WithViewStore(store.map(state: \.current)) { viewStore in NavigationLink( destination: example.exampleView(store: self.store), isActive: viewStore .binding(onChange: Action.changeCurrent) .transform(...) ) { ... } } } } TUPSFNBQͰࢠ DPNQVUFE 4UPSFΛ࡞੒ 5$"ͷ4UPSFTDPQF
  55. None
  56. ΦϒδΣΫτࢦ޲ํࣜ 
 (Multi-ViewModels)

  57. ViewController ViewModel VC Builder Router UseCase APIClient func showNext State

    VC Builder Shows next ViewController Owns Weakly references Calls Builds #VJMEFS6TF$BTF3PVUFSύλʔϯ
  58. // ैདྷઃܭɿ ViewController, ViewModel, UseCase, Router Λͭͳ͗͜ΉϏϧμʔํࣜ func build(apiClient: any

    APIClientProtocol) -> UIViewController { let useCase = UseCase(apiClient: apiClient) let viewModel = ViewModel(useCase: useCase) let viewController = ViewController(viewModel: viewModel) let router = Router(viewController: viewController) viewController.router = router return viewController } // ViewController ͕ݺͼग़͢ϧʔςΟϯάॲཧ struct Router: RouterProtocol { weak var viewController: UIViewController? func handleRoute1(...) { ... } // ը໘ભҠύλʔϯ1 func handleRoute2(...) { ... } // ը໘ભҠύλʔϯ2 }
  59. ViewController RouteStore VC Builder Router (closure) Environment fetch Reducer State

    Action Route Publisher VC Builder Shows next ViewController Sends Route Weakly references Builds 3PVUF4UPSF JO"DUPNBUPO
  60. // Actomaton RouteStore Λ࢖ͬͨ Environment, Store, Router ͷͭͳ͗͜Έ func build(apiClient:

    any APIClientProtocol) -> UIViewController { let environment = Environment(fetch: { request in return apiClient.fetch(request) }) let store = RouteStore<Action, State, Environment, Route>( state: State(), reducer: reducer environment: environment ) let viewController = ViewController(store: store) store.subscribeRoutes { [weak viewController] route in ... } // ը໘ભҠॲཧ return viewController } // ը໘ભҠύλʔϯͷܕΛ༻ҙ͢Δ enum Route { case route1(...) case route2(...) } ϧʔςΟϯάॲཧ͕Ͱ͖Δ 3PVUF4UPSFΛ࢖͏ &OWJSPONFOU͸ 
 6TF$BTFͱಉ͡ %*Մೳ
  61. let reducer = Reducer { action, state, environment in switch

    action { case .fetch: state.isLoading = true return Effect(queue: FetchEffectQueue()) { let response = try await environment.fetch(Request(...)) return Action._didFetch(response) } case let ._didFetch(response): state.isLoading = false return Effect.fireAndForget { print(response) } case .showNext: return Effect.fireAndForget { environment.sendRoute(Route.route1(...)) } } } 3FEVDFSͷதͷFOJWPSONFOU͕ TFOE3PVUFΛ࢖͏͜ͱ͕Ͱ͖Δ
  62. Action 􀉭 Real View Reducer State Virtual View Effect 💥

    Runtime Action 􀉭 Real View Reducer State Virtual View Effect 💥 Runtime Screen 1 Screen 2 ֤ը໘͝ͱʹ 
 &MN"SDIJUFDUVSF3VOUJNF 
 3PVUF4UPSF Λ༻ҙ͢Δ
  63. Action 􀉭 Real View Reducer State Virtual View Effect 💥

    Runtime Action 􀉭 Real View Reducer State Virtual View Effect 💥 Runtime Screen 2 Shows next ViewController + Messaging forward Messaging backward sendRoute sendRoute ը໘ؒͷσʔλͷ΍ΓऔΓ ը໘ભҠΛؚΉ  ΛTFOE3PVUFΛ࢖࣮ͬͯߦ
  64. ΞΫλʔϞσϧͱ Elm Architecture ͷ༥߹

  65. ≒ ΦϒδΣΫτࢦ޲ͱ ؔ਺ܕϓϩάϥϛϯάͷ༥߹

  66. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect
  67. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect ؔ਺ܕϓϩάϥϛϯά
  68. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect ΦϒδΣΫτࢦ޲ ϓϩάϥϛϯά
  69. ϜʔΞɾϚγϯ&MN"SDIJUFDUVSF indirect enum Moore<I, A> { case runMoore(A, I ->

    Moore<I, A>) } Moore<Action, View> ≅ (View, Action -> Moore<Action, View>) ≅ νX. (View, Action -> X) // ࠷େෆಈ఺ νX.F<X> = ∃S. (S, (S -> F<S>)) ≅ ∃S. (S, (S -> (View, Action -> S))) ≅ ∃S. (S, S -> View, S -> Action -> S) ≅ ∃S. (initialState: S, reducer: S -> Action -> S, render: S -> View) ϜʔΞɾϚγϯ ೖྗʹґଘ͠ͳ͍ঢ়ଶػց ৗʹग़ྗՄೳͳ"ͱ ೖྗ*͔Βࣗ਎΁ͷঢ়ଶભҠͷ 
 ϖΞΛ༨ؼೲతʹఆٛͨ͠ܕ J04%$ϓϨθϯࢀর
  70. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect Elm Architecture = ॳظঢ়ଶ × Reducer × render
  71. Comonadic UI = Comonad × ࢀরΦϒδΣΫτ ʮ$PNPOBE ؔ਺ܕ ʯͱʮࢀরʯΛ࢖ͬͯ ΦϒδΣΫτࢦ޲6*ͷੈքΛ࠶ݱ͢Δ

  72. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect
  73. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect Comonad ʹΑΔ ؔ਺ܕϓϩάϥϛϯά
  74. Msg (Action) 􀉭 Real View Update (Reducer) State Virtual View

    view (render) Diff (New State) User Action Cmd (Effect) 💥 Runtime I/O Run Side-effect ΞΫλʔϞσϧ (ࢀর) ʹΑΔ ΦϒδΣΫτࢦ޲ ϓϩάϥϛϯά
  75. Actomaton = Moore (Elm) × ΞΫλʔϞσϧ

  76. ༨ஊ ϛʔϦɾϚγϯΛ࢖ͬͨΦϒδΣΫτࢦ޲ͷղऍ wDG)BTLFMMͰͷ߹੒ՄೳͳΦϒδΣΫτͷߏ੒ͱͦͷԠ༻ ໦ԼҮষ ࢁຊ࿨඙   w0CKFDU͸ʮܭࢉޮՌͷݍʹ͓͚Δࣹʯͱͯ͠ѻ͑Δ ߹੒Մೳ 

    wΧϓηϧԽɾܧঝɾΦʔόʔϥΠυΛ࣮ݱ͢Δ indirect enum Mealy<I, A> { case runMealy(I -> (A, Mealy<I, A>)) } ϛʔϦɾϚγϯ ೖྗ*͕൐ͬͯ ग़ྗ"ͱࣗ਎΁ͷঢ়ଶભҠ Λฦ͢ɺ༨ؼೲతʹఆٛͨ͠ܕ indirect enum Object<Msg> { case runObject<A>(Msg<A> -> IO<(A, Object<Msg>)>) } .TH͔Β*0΁ͷࣗવม׵
  77. ༨ஊ ϛʔϦɾϚγϯΛ࢖ͬͨΦϒδΣΫτࢦ޲ͷղऍ wDG)BTLFMMͰͷ߹੒ՄೳͳΦϒδΣΫτͷߏ੒ͱͦͷԠ༻ w0CKFDU͸ʮܭࢉޮՌͷݍʹ͓͚Δࣹʯͱͯ͠ѻ͑Δ ߹੒Մೳ  wΧϓηϧԽɾܧঝɾΦʔόʔϥΠυΛ࠶ݱ͢Δ indirect enum Mealy<I,

    A> { case runMealy(I -> (A, Mealy<I, A>)) } ϛʔϦɾϚγϯ ೖྗ*͕൐ͬͯ ग़ྗ"ͱࣗ਎΁ͷঢ়ଶભҠ Λฦ͢ɺ༨ؼೲతʹఆٛͨ͠ܕ indirect enum Object<Msg> { case runObject<A>(Msg<A> -> IO<(A, Object<Msg>)>) } .TH͔Β*0΁ͷࣗવม׵ ؔ਺ܕϓϩάϥϛϯά͔Β ΦϒδΣΫτࢦ޲΁ ʙ ༨ؼೲɾ༨୅਺ɾঢ়ଶػց ʙ
  78. ·ͱΊ

  79. ·ͱΊ wΞΫλʔϞσϧͱ&MN"SDIJUFDUVSF w"DUPNBUPO ͭͷܭࢉϞσϧͷ༥߹  w&MN"SDIJUFDUVSF͸.77.Λ࠶ݱ͢Δ w ༨ؼೲΛ࢖ͬͨ ؔ਺ܕϓϩάϥϛϯά͸ɺ 


    ঢ়ଶػցΛͳ͢ ΦϒδΣΫτࢦ޲ϓϩάϥϛϯάΛ 
 ࠶ݱ͢Δ
  80. Thanks! Yasuhiro Inami @inamiy