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

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

    View Slide

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

    &MN"SDIJUFDUVSF ؔ਺ܕϓϩάϥϛϯά
    ͷ༥߹

    View Slide

  3. ΞΫλʔϞσϧ

    View Slide

  4. View Slide

  5. Swift Concurrency

    1. async /await


    2. Structured Concurrency


    3. Actors

    View Slide

  6. View Slide

  7. actor Counter {


    var count: Int = 0 // ಺෦ঢ়ଶ


    func increment() { // τϥϯβΫγϣϯ


    count += 1 // ঢ়ଶߋ৽


    print(count) // ෭࡞༻ͷ࣮ߦ


    }


    }


    // ΞΫλʔͷ࢖༻ྫ


    let counter = Counter()


    // ΞΫλʔ֎෦͔ΒΞΫλʔʹΞΫηε͢Δ৔߹ɺ async / await ඇಉظॲཧʹͳΔ


    await counter.increment()
    ΞΫλʔͷྫ ΞΫλʔঢ়ଶͱͦͷૢ࡞ΛҰՕॴʹ·ͱΊͨ
    ʮεϨου҆શʯͳΦϒδΣΫτ
    ΞΫλʔִ཭͞Εͨϝιου
    εϨου҆શΛอূ͢Δ
    τϥϯβΫγϣϯ

    View Slide

  8. protocol CounterProtocol: Actor {


    func increment() // τϥϯβΫγϣϯͷந৅


    }


    actor Counter {


    var count: Int = 0 // ಺෦ঢ়ଶ


    }


    extension Counter: CounterProtocol {


    func increment() { // τϥϯβΫγϣϯͷ࣮૷


    count += 1 // ঢ়ଶߋ৽


    print(count) // ෭࡞༻ͷ࣮ߦ


    }


    }
    ΞΫλʔͷྫ 1SPUPDPMʹΑΔந৅Խ

    QSPUPDPMʹΑΔ

    ۩ମΞΫλʔͷ
    ϝιουͷந৅Խ
    QSPUPDPM४ڌʹΑΔ

    ந৅ϝιουͷ࣮૷

    View Slide

  9. Elm Architecture

    View Slide

  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

    View Slide

  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ͷதͰ࣮ߦ͢Δ

    View Slide

  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 ͕ओ໾)

    View Slide

  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) // ෭࡞༻ͷ࣮ߦ


    }


    }

    View Slide

  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 = ؔ਺ܕ


    ྆ऀʹີ઀ͳؔ܎͕͋Δʂ

    View Slide

  15. ΞΫλʔϞσϧͱ&MN"SDIJUFDUVSF
    wΞΫλʔϞσϧ ΦϒδΣΫτࢦ޲ϓϩάϥϛϯά

    w௚ྻॲཧͷ&YFDVUPSΛ࣋ͭɺίϯύΠϧ࣌εϨου҆શ
    ੑ͕อূ͞ΕͨΦϒδΣΫτ
    wΦϒδΣΫτ܈ͷ෼ࢄ؅ཧʹڧ͍ʢଟਆڭʣ
    w&MN"SDIJUFDUVSF ؔ਺ܕϓϩάϥϛϯά

    wΞΫγϣϯɺঢ়ଶɺ3FEVDFSͷ୯७σʔλߏ଄Λ߹੒
    w4JOHMF4PVSDFPG5SVUIΛߏ੒Ͱ͖ΔʢҰਆڭʣ

    View Slide

  16. ࠷ۙͷ4XJGUJ04։ൃͷτϨϯυ
    .77. 7JFX.PEFM
    ʹΞΫλʔϞσϧΛద༻
    w !.BJO"DUPSʹΑΔϝΠϯεϨου҆શੑ
    $PNQPTBCMF"SDIJUFDUVSF 5$"
    &MNελΠϧ

    w ঢ়ଶͷू໿؅ཧʹΑΔςετɾσόοΪϯά
    🤔
    2྆ํͷྑ͍ॴऔΓ͕Ͱ͖ͳ͍΋ͷ͔ʜʁ

    View Slide

  17. Actomaton

    http://github.com/Actomaton/Actomaton

    View Slide

  18. J04%$+BQBO
    3FBDUJWF4UBUF.BDIJOF
    IUUQTTQFBLFSEFDLDPNJOBNJZSFBDUJWFTUBUF
    NBDIJOFKBQBOFTF
    J04%$+BQBO

    4XJGU6*࣌୅ͷ

    'VODUJPOBMJ04"SDIJUFDUVSF
    IUUQTTQFBLFSEFDLDPNJOBNJZJPTED
    KBQBO

    View Slide

  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ΞϓϦͰಋೖ࣮੷

    View Slide

  20. Demo

    http://github.com/Actomaton/Actomaton-Gallery

    View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. ࢀߟɿ4XJGU6*ͰඥΛදݱ͢Δ
    IUUQT[FOOEFWJLFIBSUJDMFTBGFGEBCG
    IUUQTUXJUUFSDPN%-9TUBUVT

    View Slide

  27. View Slide

  28. View Slide

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

    wঢ়ଶࠩ෼ͷσόοάϩά
    wλΠϜτϥϕϧʹΑΔ6OEP3FEP
    w෭࡞༻ͷΩϡʔ؅ཧ &
    ff
    FDU2VFVF
    &MN$PNQPTBCMF"SDIJUFDUVSF

    ʹ͸උΘ͍ͬͯͳ͍ಠࣗػೳ

    View Slide

  30. Actomaton ͷ࢖͍ํ

    View Slide

  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͸ʮ७ਮؔ਺ʯ

    View Slide

  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) // ෭࡞༻ͷ࣮ߦ


    }


    }


    }

    View Slide

  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॥
    ؀ࢀর͕ى͖ͳ͍҆શઃܭʹͳΔ

    View Slide

  34. let store = Store(


    state: State(), // ॳظঢ়ଶ


    reducer: reducer


    )


    struct ContentView: View {


    let store: Store


    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


    ʹม׵ͯ͠࢖༻

    View Slide

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

    Λߏ੒͢ΔͨΊͷܕͱϩδοΫ
    ؔ਺ܕελΠϧ


    (߹੒͠΍͍͢)
    ΦϒδΣΫτࢦ޲
    (߹੒͠ʹ͍͘)
    Reducer
    State
    Action

    View Slide

  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
    ґଘΛ஫ೖ͢Δܕ

    View Slide

  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Λ࢖ͬͯ
    ෭࡞༻Λ࣮ߦ

    View Slide

  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*%Λઃఆ͠ɺ
    ΩϟϯηϧՄೳʹ͢Δ

    View Slide

  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Λઃఆ͠ɺ
    ෳ਺ͷ෭࡞༻ΛΩϡʔͰ؅ཧ͢Δ

    View Slide

  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 { ... }

    View Slide

  41. %FGBVMU .FSHF
    0MEFTU4VTQFOE/FX
    /FXFTU 0MEFTU%JTDBSE/FX

    View Slide

  42. EffectQueue


    (Newest1)
    EffectQueue


    (Oldest1DiscardNew)
    EffectQueue


    (Oldest1SuspendNew)
    Store
    Reducer
    State Effect
    Action
    View
    Feedback
    Action
    Running
    Effects
    Environment

    View Slide

  43. Actomaton Λ࢖ͬͨ

    ΞϓϦͷઃܭࢦ਑

    View Slide

  44. "DUPNBUPOΛ࢖ͬͨΞϓϦͷઃܭࢦ਑
    ؔ਺ܕϓϩάϥϛϯάํࣜ 4JOHMF4PVSDFPG5SVUI

    w"DUJPO 4UBUF 3FEVDFSͷ߹੒ͱ4UPSFͷ෼ղɾ࠷దԽ
    w௕ॴɿঢ়ଶͷू໿؅ཧɺ୹ॴɿ߹੒ͱ෼ղͷ೉қ౓͕ߴ͍
    ΦϒδΣΫτࢦ޲ํࣜ .VMUJ7JFX.PEFMT

    w4UPSFΛ7JFX.PEFMͱಉҰࢹ͠ɺ߹੒ͱ෼ղΛߦΘͳ͍
    w௕ॴɿ୭Ͱ΋ཧղͰ͖Δɺ୹ॴɿঢ়ଶͷ෼ࢄ؅ཧ
    ΞϓϦશମͰ
    4UPSF͕ͭ
    ֤ը໘͝ͱʹ
    4UPSFΛ༻ҙ

    View Slide

  45. ؔ਺ܕϓϩάϥϛϯάํࣜ

    (Single Source of Truth)

    View Slide

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

    Λߏ੒͢ΔͨΊͷܕͱϩδοΫ
    ؔ਺ܕελΠϧ


    (߹੒͠΍͍͢)
    ΦϒδΣΫτࢦ޲
    (߹੒͠ʹ͍͘)
    Reducer
    State
    Action

    View Slide

  47. Reducer
    State
    Action
    (CounterLogic)
    Reducer
    State
    Action
    (DownloaderLogic)
    Reducer
    State
    Action
    (PhysicsLogic)
    Environment Environment Environment
    ֤αϯϓϧը໘ͷϩδοΫ

    View Slide

  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୯ҐͰ߹੒

    View Slide

  49. Reducer
    State
    Action
    AppView
    Store
    (AppLogic)
    ΞϓϦͷશͯΛ໢ཏ͢Δ

    ܕͱϩδοΫ
    Reducer
    State
    Action
    Environment Environment
    4JOHMF4PVSDF
    PG5SVUIͳ
    །Ұਆͷ஀ੜ

    View Slide

  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͔Β
    ࢉग़ͨ͠΋ͷ

    View Slide

  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 ͳ

    ΦϒδΣΫτΛ֤ը໘ʹ෼ղ͢Δ

    View Slide

  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Ͱఆٛ͢Δ

    View Slide

  53. let appReducer = Reducer.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%$ϓϨθϯࢀর

    View Slide

  54. @MainActor struct AppView: View {


    let store: Store


    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

    View Slide

  55. View Slide

  56. ΦϒδΣΫτࢦ޲ํࣜ

    (Multi-ViewModels)

    View Slide

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


    ViewController
    Owns
    Weakly


    references
    Calls
    Builds
    #VJMEFS6TF$BTF3PVUFSύλʔϯ

    View Slide

  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


    }

    View Slide

  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

    View Slide

  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(


    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ͱಉ͡ %*Մೳ

    View Slide

  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Λ࢖͏͜ͱ͕Ͱ͖Δ

    View Slide

  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
    Λ༻ҙ͢Δ

    View Slide

  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Λ࢖࣮ͬͯߦ

    View Slide

  64. ΞΫλʔϞσϧͱ


    Elm Architecture ͷ༥߹

    View Slide

  65. ≒ ΦϒδΣΫτࢦ޲ͱ


    ؔ਺ܕϓϩάϥϛϯάͷ༥߹

    View Slide

  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

    View Slide

  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
    ؔ਺ܕϓϩάϥϛϯά

    View Slide

  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
    ΦϒδΣΫτࢦ޲


    ϓϩάϥϛϯά

    View Slide

  69. ϜʔΞɾϚγϯ&MN"SDIJUFDUVSF
    indirect enum Moore {


    case runMoore(A, I -> Moore)


    }
    Moore


    ≅ (View, Action -> Moore)


    ≅ νX. (View, Action -> X)


    // ࠷େෆಈ఺ νX.F = ∃S. (S, (S -> F))


    ≅ ∃S. (S, (S -> (View, Action -> S)))


    ≅ ∃S. (S, S -> View, S -> Action -> S)


    ≅ ∃S. (initialState: S, reducer: S -> Action -> S, render: S -> View)
    ϜʔΞɾϚγϯ
    ೖྗʹґଘ͠ͳ͍ঢ়ଶػց
    ৗʹग़ྗՄೳͳ"ͱ
    ೖྗ*͔Βࣗ਎΁ͷঢ়ଶભҠͷ

    ϖΞΛ༨ؼೲతʹఆٛͨ͠ܕ
    J04%$ϓϨθϯࢀর

    View Slide

  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

    View Slide

  71. Comonadic UI


    = Comonad × ࢀরΦϒδΣΫτ
    ʮ$PNPOBE ؔ਺ܕ
    ʯͱʮࢀরʯΛ࢖ͬͯ
    ΦϒδΣΫτࢦ޲6*ͷੈքΛ࠶ݱ͢Δ

    View Slide

  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

    View Slide

  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 ʹΑΔ


    ؔ਺ܕϓϩάϥϛϯά

    View Slide

  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
    ΞΫλʔϞσϧ (ࢀর) ʹΑΔ


    ΦϒδΣΫτࢦ޲


    ϓϩάϥϛϯά

    View Slide

  75. Actomaton


    = Moore (Elm) × ΞΫλʔϞσϧ

    View Slide

  76. ༨ஊ ϛʔϦɾϚγϯΛ࢖ͬͨΦϒδΣΫτࢦ޲ͷղऍ

    wDG)BTLFMMͰͷ߹੒ՄೳͳΦϒδΣΫτͷߏ੒ͱͦͷԠ༻ ໦ԼҮষ ࢁຊ࿨඙

    w0CKFDU͸ʮܭࢉޮՌͷݍʹ͓͚Δࣹʯͱͯ͠ѻ͑Δ ߹੒Մೳ

    wΧϓηϧԽɾܧঝɾΦʔόʔϥΠυΛ࣮ݱ͢Δ
    indirect enum Mealy {


    case runMealy(I -> (A, Mealy))


    }
    ϛʔϦɾϚγϯ
    ೖྗ*͕൐ͬͯ
    ग़ྗ"ͱࣗ਎΁ͷঢ়ଶભҠ
    Λฦ͢ɺ༨ؼೲతʹఆٛͨ͠ܕ
    indirect enum Object {


    case runObject(Msg -> IO<(A, Object)>)


    }
    .TH͔Β*0΁ͷࣗવม׵

    View Slide

  77. ༨ஊ ϛʔϦɾϚγϯΛ࢖ͬͨΦϒδΣΫτࢦ޲ͷղऍ

    wDG)BTLFMMͰͷ߹੒ՄೳͳΦϒδΣΫτͷߏ੒ͱͦͷԠ༻
    w0CKFDU͸ʮܭࢉޮՌͷݍʹ͓͚Δࣹʯͱͯ͠ѻ͑Δ ߹੒Մೳ

    wΧϓηϧԽɾܧঝɾΦʔόʔϥΠυΛ࠶ݱ͢Δ
    indirect enum Mealy {


    case runMealy(I -> (A, Mealy))


    }
    ϛʔϦɾϚγϯ
    ೖྗ*͕൐ͬͯ
    ग़ྗ"ͱࣗ਎΁ͷঢ়ଶભҠ
    Λฦ͢ɺ༨ؼೲతʹఆٛͨ͠ܕ
    indirect enum Object {


    case runObject(Msg -> IO<(A, Object)>)


    }
    .TH͔Β*0΁ͷࣗવม׵
    ؔ਺ܕϓϩάϥϛϯά͔Β


    ΦϒδΣΫτࢦ޲΁


    ʙ ༨ؼೲɾ༨୅਺ɾঢ়ଶػց ʙ

    View Slide

  78. ·ͱΊ

    View Slide

  79. ·ͱΊ
    wΞΫλʔϞσϧͱ&MN"SDIJUFDUVSF
    w"DUPNBUPO ͭͷܭࢉϞσϧͷ༥߹

    w&MN"SDIJUFDUVSF͸.77.Λ࠶ݱ͢Δ
    w ༨ؼೲΛ࢖ͬͨ
    ؔ਺ܕϓϩάϥϛϯά͸ɺ

    ঢ়ଶػցΛͳ͢
    ΦϒδΣΫτࢦ޲ϓϩάϥϛϯάΛ

    ࠶ݱ͢Δ

    View Slide

  80. Thanks!


    Yasuhiro Inami


    @inamiy

    View Slide