## Slide 1

### Slide 1 text

ϝϧΧϦ ΞοςΛࢧ͑Δ ΦʔτϚτϯ Vu Nhat Minh / @orakaro Souzoh iOS Talk

## Slide 2

### Slide 2 text

Mercari Atte Scala / Swift Engineer / @orakaro

## Slide 3

### Slide 3 text

Ξος͕औΓ૊ΜͰ͍Δ͜ͱ • ϑϧRxSwift • Driverʹੵۃతʹஔ͖׵͑Δ • ViewModelͷΠϯϓοτɺΞ΢τϓοτԽ • ܧঝΑΓίϯϙδγϣϯ • ΦʔτϚτϯ

## Slide 4

### Slide 4 text

εςʔτϚγϯͷ͓͞Β͍ • ༗ݶΦʔτϚτϯ • ભҠؔ਺ͷΈ (s, a) -> s • Mealy Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ (s, a) -> b • Moore Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ s -> b

## Slide 5

### Slide 5 text

༗ݶΦʔτϚτϯ • ༗ݶΦʔτϚτϯ • ભҠؔ਺ͷΈ (s, a) -> s • Mealy Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ (s, a) -> b • Moore Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ s -> b ঢ়ଶΛ؅ཧ͍ͨ࣌͠ʹ׆༻͢΂͖

## Slide 6

### Slide 6 text

Binary Gap A binary gap within a positive integer N is any maximal sequence of consecutive zeros that is surrounded by ones at both ends in the binary representation of N. Ex: 9 = 1001 → 2

## Slide 7

### Slide 7 text

A binary gap within a positive integer N is any maximal sequence of consecutive zeros that is surrounded by ones at both ends in the binary representation of N. Ex: 9 = 1001 → 2 Started Zero One Binary Gap

## Slide 8

### Slide 8 text

Visual Format Language class func constraints(withVisualFormat format: String, options opts: NSLayoutFormatOptions = [], metrics: [String : Any]?, views: [String : Any]) -> [NSLayoutConstraint] "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|"

## Slide 9

### Slide 9 text

Visual Format Language class func constraints(withVisualFormat format: String, options opts: NSLayoutFormatOptions = [], metrics: [String : Any]?, views: [String : Any]) -> [NSLayoutConstraint] "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|" Direction Start Line Number Label [ ] ( ) End

## Slide 10

### Slide 10 text

݈શͳίʔυϕʔεͷͨΊʹ ڊେif elseΛʢͰ͖Ε͹ʣॻ͔ͳ͍ άϩʔόϧม਺ΛʢͰ͖Ε͹ʣ࢖Θͳ͍ ؔ਺ ΛʢͰ͖Δ͚ͩʣ७ਮؔ਺ʹ͍ͨ͠

## Slide 11

### Slide 11 text

ձһొ࿥ϑϩʔ ʁ ʁ ʁ ʁ ʁ

## Slide 12

### Slide 12 text

ձһొ࿥ϑϩʔ Emailొ࿥͍ͤͨ͞ Facebookೝূ͍ͤͨ͞ ϝϧΧϦొ࿥ࡁΈͳΒϫϯΫϦοΫ ύεϫʔυ࠶ઃఆ ϩάΠϯ͍ͤͨ͞

ձһొ࿥ϑϩʔ

ొ࿥ঢ়ଶ൑ఆ

ϝʔϧϩάΠϯ

## Slide 19

### Slide 19 text

ભҠϩδοΫ͸υϝΠϯ஌ࣝ Ϟσϧ૚Ͱ؅ཧ͢΂͖ MVC / MVP / MVVM

## Slide 20

### Slide 20 text

΋͸΍֤ը໘͕ εςʔτΛදݱ͍ͯ͠Δͷ͡Ό

## Slide 22

### Slide 22 text

RegistrationStateViewController protocol RegistrationStateViewController { var state: RegistrationState { get } func nextViewController(event: RegistrationEvent) -> UIViewController? } extension RegistrationStateViewController { func nextViewController(event: RegistrationEvent) -> UIViewController? { let machine = AutomatonManager.registrationMachine guard let state = machine.transition(from: state, by: event) else { return nil } switch state { //... } } ΦʔτϚτϯ

## Slide 23

### Slide 23 text

TransitionΛఆٛ struct Transition { let from: S let to: S let by: E } from to by State State Event

## Slide 24

### Slide 24 text

State MachineΛ࣮૷ class Automaton { var routes: [S: [E: S]] = [:] init(initialState: S, transitions: [Transition]) { for t in transitions { addRoute(t) } } private func addRoute(_ t: Transition) { var dict = routes[t.from] ?? [:] dict[t.by] = t.to routes[t.from] = dict } func transition(from: S, by: E) -> S? { guard let next = routes[from].flatMap({ \$0[by] }) else { return nil } return next } }

## Slide 25

### Slide 25 text

State MachineΛ࣮૷ class Automaton { var routes: [S: [E: S]] = [:] init(initialState: S, transitions: [Transition]) { for t in transitions { addRoute(t) } } private func addRoute(_ t: Transition) { var dict = routes[t.from] ?? [:] dict[t.by] = t.to routes[t.from] = dict } func transition(from: S, by: E) -> S? { guard let next = routes[from].flatMap({ \$0[by] }) else { return nil } return next } } ࣙॻܕΛ࢖ͬͯભҠΛ؅ཧ͢Δ

## Slide 26

### Slide 26 text

εςʔτͱΠϕϯτΛఆٛ enum RegistrationState { case root case registerRoot case profileRegister case startWithMercari case emailLogin case atteLogin case home } enum RegistrationEvent: Hashable { case showRegisterRoot(loginMode: RootViewController.LoginMode?) case registerWithFacebook(profile: FacebookProfile?) case startWithMercari(resource: MercariIdResource?, tokenPair: TokenPair?, showsCloseButton: Bool) case loginWithEmail(showsCancelButton: Bool) case loginWithAtte(resource: MercariIdResource?, tokenPair: TokenPair?) case loginAndShowHome(tokenPair: TokenPair?, userId: Int64, hasMercariItem: Bool) }

## Slide 27

### Slide 27 text

ભҠάϥϑΛఆٛ let registrationGraph: [Transition] = [ Transition(from: .root, to: .registerRoot, by: DefaultEvent.showRegisterRoot), Transition(from: .registerRoot, to: .profileRegister, by: DefaultEvent.registerWithFacebook), Transition(from: .registerRoot, to: .startWithMercari, by: DefaultEvent.startWithMercari), Transition(from: .registerRoot, to: .emailLogin, by: DefaultEvent.loginWithEmail), Transition(from: .registerRoot, to: .atteLogin, by: DefaultEvent.loginWithAtte), Transition(from: .registerRoot, to: .home, by: DefaultEvent.loginAndShowHome), ] let registrationMachine = Automaton( initialState: .root, transitions: registrationGraph )

## Slide 28

### Slide 28 text

ભҠϩδοΫ͚ͩςετͰ͖Δ describe("ΞϓϦىಈ࣌τʔΫϯ͕ଘࡏ͠ͳ͍") { it("ϝʔϧͰ৽نొ࿥͕Ͱ͖Δ") { self.tryRoute(events: [ DefaultEvent.showRegisterRoot, DefaultEvent.registerWithEmail, DefaultEvent.showProfileRegister, DefaultEvent.confirmSMS, DefaultEvent.loginAndShowHome ]) XCTAssert(self.currentState == .home) } } //... private func tryRoute(events: [RegistrationEvent]) { for event in events { self.currentState.map { state in self.currentState = self.machine.transition(from: state, by: event) } } }

No content

·ͩ໰୊͕͋Δ

## Slide 31

### Slide 31 text

RegistrationStateViewController protocol RegistrationStateViewController { var state: RegistrationState { get } func nextViewController(event: RegistrationEvent) -> UIViewController? } extension RegistrationStateViewController { func nextViewController(event: RegistrationEvent) -> UIViewController? { let machine = AutomatonManager.registrationMachine guard let state = machine.transition(from: state, by: event) else { return nil } switch state { //... } } ͜͜ͰViewControllerΛੜ੒͢Δͷʁ

## Slide 32

### Slide 32 text

RegistrationStateViewController switch state { case .root: return RootViewController.make(withDependency: ()) case .registerRoot: return RegisterRootViewController.make(withDependency: ()) case .profileRegister : switch event { case .registerWithFacebook(.some(let profile)): return ProfileRegisterViewController.make(withDependency: .init(facebookProfile: profile)) default: return nil } case .startWithMercari: switch event { case .startWithMercari(.some(let resource), .some(let token)): return StartWithMercariViewController.make(withDependency: .init(resource: resource, token: token)) default: return nil } case .emailLogin: return EmailLoginViewController.make(withDependency: ()) case .atteLogin: switch event { case .loginWithAtte(.some(let resource), .some(let token)): return AtteLoginViewController.make(withDependency: .init(resource: resource, token: token)) default: return nil } case .home: switch event { case .loginAndShowHome(.some(let token), let userId): return HomeViewController.make(withDependency: .init(token: token, userId: userId)) default: return nil } default: return nil } ڊେ if elseͱେͯ͠มΘΜͳ͍…

## Slide 33

### Slide 33 text

ભҠάϥϑΛఆٛ let registrationGraph: [Transition] = [ Transition(from: .root, to: .registerRoot, by: DefaultEvent.showRegisterRoot), Transition(from: .registerRoot, to: .profileRegister, by: DefaultEvent.registerWithFacebook), Transition(from: .registerRoot, to: .startWithMercari, by: DefaultEvent.startWithMercari), Transition(from: .registerRoot, to: .emailLogin, by: DefaultEvent.loginWithEmail), Transition(from: .registerRoot, to: .atteLogin, by: DefaultEvent.loginWithAtte), Transition(from: .registerRoot, to: .home, by: DefaultEvent.loginAndShowHome), ] let registrationMachine = Automaton( initialState: .root, transitions: registrationGraph ) DefaultEvent !?

## Slide 34

### Slide 34 text

DefaultEvent ͬͯԿʁ • Swift 3 ͷassociated value enumʹ͸σϑΥϧτ஋ઃఆͰ͖ͳ͍ • Acceptedͷεςʔλε͕ͩSwift4ʹೖ͍ͬͯͳ͍ Proposal: SE-0155 struct DefaultEvent { static let showRegisterRoot: RegistrationEvent = .showRegisterRoot() static let registerWithFacebook: RegistrationEvent = .registerWithFacebook(profile: nil) static let startWithMercari: RegistrationEvent = .startWithMercari(resource: nil, token: nil) static let loginWithEmail: RegistrationEvent = .loginWithEmail() static let loginWithAtte: RegistrationEvent = .loginWithAtte(resource: nil, token: nil) static let loginAndShowHome: RegistrationEvent = .loginAndShowHome(token: nil, userId: 0) }

ΦʔτϚτϯ + FP

## Slide 37

### Slide 37 text

ભҠؔ਺ (aka Reducer) • ༗ݶΦʔτϚτϯ • ભҠؔ਺ͷΈ (s, a) -> s • Mealy Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ (s, a) -> b • Moore Machine • ભҠؔ਺ (s, a) -> s • ग़ྗؔ਺ s -> b

## Slide 38

### Slide 38 text

ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ (s, a) -> b Mealy Machine / Moore Machine ભҠؔ਺ (s, a) -> (s, b) ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ s -> b ભҠؔ਺ s -> (a -> s, b)

## Slide 39

### Slide 39 text

ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ (s, a) -> b Mealy Machine vs Elm ભҠؔ਺ (s, a) -> (s, b) Elm Architecture fun update(state: State, action: Action): Pair

## Slide 40

### Slide 40 text

ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ (s, a) -> b Mealy Machine ભҠؔ਺ (s, a) -> (s, b) ભҠؔ਺ a -> s -> (s, b)

## Slide 41

### Slide 41 text

ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ (s, a) -> b Mealy Machine ભҠؔ਺ (s, a) -> (s, b) ભҠؔ਺ a -> s -> (s, b) Stateful Computation !!

## Slide 42

### Slide 42 text

ঢ়ଶ෇͖ͷܭࢉʢStateful Computationʣ ঢ়ଶ෇͖ͷܭࢉͱ͸ɺ͋Δঢ়ଶΛऔͬͯɺߋ৽͞Εͨঢ়ଶͱҰॹʹܭࢉ݁ՌΛ ฦؔ͢਺ͱͯ͠දݱͰ͖Δɿ s -> (s, a) class State { private let run: (S) -> (S, A) init(f: @escaping (S) -> (S, A)) { self.run = f } func run(s: S) -> (S, A) { return self.run(s) } }

## Slide 43

### Slide 43 text

εςʔτϞφυ ঢ়ଶ෇͖ͷܭࢉͱ͸ɺ͋Δঢ়ଶΛऔͬͯɺߋ৽͞Εͨঢ়ଶͱҰॹʹܭࢉ݁ՌΛ ฦؔ͢਺ͱͯ͠දݱͰ͖Δɿ s -> (s, a) extension State { func map(g: @escaping (A) -> B) -> State { return State { s in let (s1, val) = self.run(s: s) return (s1, g(val)) } } func flatMap(g: @escaping (A) -> State) -> State { return State { s in let (s1, val) = self.run(s: s) return g(val).run(s: s1) } } }

## Slide 44

### Slide 44 text

Ϟφυ = ﬂatmappable ܕ Image credit: Functors, Applicatives, And Monads In Pictures

## Slide 46

### Slide 46 text

ભҠؔ਺ (s, a) -> s ग़ྗؔ਺ (s, a) -> b Mealy Machine ભҠؔ਺ (s, a) -> (s, b) ભҠؔ਺ a -> s -> (s, b) ભҠؔ਺ a -> State[s, b] εςʔτϞφυ

## Slide 47

### Slide 47 text

MonadicAutomaton class MonadicAutomaton { typealias T = (A) -> State private var f : T init(f: @escaping T) { self.f = f } func transition(from: S, by: A) -> (S, B) { return f(by).run(s: from) } } ɹભҠؔ਺ A -> State[S, B] A:ΠϯϓοτܕɺB: Ξ΢τϓοτܕ A = ΠϕϯτenumɺB = UIViewController

## Slide 48

### Slide 48 text

εςʔτͱΠϕϯτΛఆٛ enum RState { case any case root case registerRoot case profileRegister case startWithMercari case emailLogin case atteLogin case home } enum REvent { case showRegisterRoot case registerWithFacebook(profile: FacebookProfile) case startWithMercari(resource: MercariIdResource, token: String) case loginWithEmail case loginWithAtte(resource: MercariIdResource, token: String) case loginAndShowHome(token: String, userId: Int64) } OptionalͰ͸ͳ͍ʂʂ

## Slide 49

### Slide 49 text

ભҠάϥϑΛఆٛ let transitionFunc: (REvent) -> State = { event in switch event { case .registerWithFacebook(let profile): let vc = ProfileRegisterViewController.make(withDependency: .init(facebookProfile: profile)) return State { s in let s1: RState = s == .registerRoot ? .profileRegister : .any return (s1, vc) //... } } let registrationMachine = MonadicAutomaton(f : transitionFunc) A S B A S B

ΦʔτϚτϯ + FRP

## Slide 51

### Slide 51 text

ΦʔτϚτϯ + FRP • ը໘಺ͷUIมԽ΋εςʔτʹදݱ͢Δ • ΠϕϯτΛετϦʔϜʹද͢ • ReactiveSwift • inamiy/ReactiveAutomaton • RxSwift • kzaher/RxFeedback

## Slide 52

### Slide 52 text

·ͱΊ • ΦʔτϚτϯͷԠ༻ • ༗ݶΦʔτϚτϯ • Mealy Machine • Moore Machine • ঢ়ଶ؅ཧʹ༏ΕΔ • ؆୯ͳભҠॲཧͳΒFSM • Mealy Machine = State Monad