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

CodeFest 2019. Евгений Ёлчев (RedMadRobot) — Функциональный Swift — это просто

CodeFest
April 06, 2019

CodeFest 2019. Евгений Ёлчев (RedMadRobot) — Функциональный Swift — это просто

— ФП — это не сложно;
— Мы уже используем ФП и можем использовать ещё активнее;
— Элементы ФП в Swift;
— Варианты использования без предварительной подготовки команды.

CodeFest

April 06, 2019
Tweet

More Decks by CodeFest

Other Decks in Technology

Transcript

  1. О чем я буду говорить ▪ Функциональная парадигма ▪ Функциональные

    инструменты (Функторы, монады) ▪ Работа с UI =2
  2. Обычная функция func summ(a: Int, b: Int) -> Int {

    return a + b + z } let x = summ(a: 2, b: 3) =8
  3. Чистые функции ▪ Не зависят от глобального состояния ▪ Не

    создают сайд эфектов ▪ Все математические функции чистые =11
  4. «Чистый» класс class User { let name: String let surname:

    String let email: String func getFullname() -> String { return name + " " + surname } func change(email: String) -> User { return User(name: name, surname: surname, email: email) } } =12
  5. «Чистый» класс func getFullname() -> String { return name +

    " " + surname } func change(email: String) -> User { return User(name: name, surname: surname, email: email) } =14
  6. Плюсы/минусы чистоты ➕ Проще удержать в голове ➕ Проще отлаживать

    ➕ ⛓ Проще распаралеливать ➕ Проще тестировать ➖ Больше кода ➖ ⚡ Большее потребление ресурсов =17
  7. Грязь, она по всюду ▪ Сетевые запросы ▪ Файлы ▪

    БД ▪ Датчики (локация, камера, …) ▪ UI =18
  8. Постоянные проверки =22 let firstInt = 3 let intFromServer: Any?

    = getIntFormServer() let summ: Any? if let any = intFromServer, let int = any as? Int { summ = summ(a: firstInt, b: int) } else { summ = nil }
  9. Optional =24 let firstInt = 3 let intFromServer: Int? =

    getIntFormServer() let summ: Int? if let int = intFromServer as? Int { summ = summ(a: firstInt, b: int) }
  10. Функтор =27 let firstInt = 3 let intFromServer: Int? =

    getIntFormServer() let result = intFromServer.map { summ(a: firstInt, b: $0) } result.map { print($0) }
  11. Обработка нештатных ситуаций =29 let result = getIntFormServer().map { summ(a:

    3, b: $0) } result.map { print($0) } result.failure { print("Что то пошло не так") }
  12. Вычисление в несколько шагов =33 let summWith3 = summ(3) let

    result = getIntFormServer().map { summWith3($0) } let summWith3 = summ(3) let result = getIntFormServer().map(summWith3)
  13. Каррирование =34 func curry<A, B, C>(_ function: @escaping ((A, B))

    -> C) -> (A) -> (B) -> C { return { (a: A) -> (B) -> C in { (b: B) -> C in function((a, b)) } } }
  14. Апликативный функтор =37 let a: Int? = 3 let b:

    Int? = 7 let summWithA = a.map(curry(+)) let result = b.map(summWithA!)
  15. Апликативный функтор =38 extension Optional { func map<U>(_ f: ((Wrapped)

    -> U)?) -> U? { guard let f = f else { return nil } return self.map(f) } }
  16. Апликативный функтор =39 let a: Int? = 3 let b:

    Int? = 7 let summWithA = a.map(curry(+)) let result = b.map(summWithA)
  17. Апликативный функтор =40 let a: Int? = 3 let b:

    Int? = 7 let result = b.map(a.map(curry(+)))
  18. Без кастомных операторов =42 @discardableResult func zip<U, Z, Result>( _

    optional1: U?, _ optional2: Z?, _ transform: (U, Z) throws -> Result) rethrows -> Result? { guard let optional1 = optional1, let optional2 = optional2 else { return nil } return try transform(optional1, optional2) }
  19. Монада =44 let z = Optional("1").map { Int($0) } if

    let one = z, let two = one { print(two) } type(of: z) // Optional<Optional<Int>>
  20. Монада =46 let z = Optional("1").flatMap { Int($0) } if

    let one = z { print(one) } type(of: z) // Optional<Int>
  21. Плюсы/минусы монад и функторов ➕ Лаконичный синтаксис ➕ Декларативный подход

    ➕ Фокус на работе с данными, а не ошибками ➕ Безопасность ➖ Сложнее отлаживать =47
  22. Работа с UI ▪ UI - изменяемое состояние ▪ Много

    изменяемых состояний ▪ Все методы работы с UI грязные =48
  23. Текст в полях изменился @IBAction func textFieldTextDidChange() { guard let

    login = loginView.text, let password = passwordView.text else { loginButton.isEnabled = false return } let loginIsValid = login.count > constants.loginMinLenght if loginIsValid { loginView.layer.borderColor = constants.normalColor } let passwordIsValid = password.count > constants.passwordMinLenght if passwordIsValid { passwordView.layer.borderColor = constants.normalColor } loginButton.isEnabled = loginIsValid && passwordIsValid } =50
  24. Закончили ввод логина @IBAction func loginDidEndEdit() { let color: CGColor

    if let login = loginView.text, login.count > 3 { color = constants.normalColor } else { color = constants.errorColor } loginView.layer.borderColor = color } =51
  25. Закончили ввод пароля @IBAction func passwordDidEndEdit() { let color: CGColor

    if let password = passwordView.text, password.count > 6 { color = constants.normalColor } else { color = constants.errorColor } passwordView.layer.borderColor = color } =52
  26. Нажали кнопку войти @IBAction private func loginPressed() { guard let

    login = loginView.text, let password = passwordView.text else { return } auth(login: login, password: password) { [weak self] user, error in if let user = user { /* успех */ } else if error is AuthError { guard let `self` = self else { return } self.passwordView.layer.borderColor = self.constants.errorColor self.loginView.layer.borderColor = self.constants.errorColor } else { /* Другие ошибки */ } } } =53
  27. Минусы ▪ 4 метода изменяют состояния полей ▪ 2 метод

    изменяют состояние кнопки «Войти» ▪ 4 раза производится раскрытие опционального значения каждого поля ввода ▪ Не очевидны конечные состояния ▪ Нелинейный flow =54
  28. Текущее состояние UI struct LoginOutputModel { let login: String let

    password: String var loginIsValid: Bool { return login.count > 3 } var passwordIsValid: Bool { return password.count > 6 } var isValid: Bool { return loginIsValid && passwordIsValid } } =57
  29. Модель изменений UI struct LoginInputModel { let loginBorderColor: CGColor? let

    passwordBorderColor: CGColor? let loginButtonEnable: Bool? let popupErrorMessage: String? } =58
  30. События меняющие UI enum Event { case textFieldTextDidChange case loginDidEndEdit

    case passwordDidEndEdit case loginPressed case authFailure(Error) } =59
  31. Нажали кнопку войти @IBAction private func loginPressed() { updateView(.loginPressed) let

    completion: (User?, Error?) -> Void = { [weak self] user, error in user.map { _ in /* успех */ } zip(self, error) { $0.updateView(.authFailure($1)) } } outputModel.map { auth(login: $0.login, password: $0.password, completion: completion) } } =63
  32. Изменяем UI func updateView(_ event: Event) { let inputModel =

    makeInputModel(event: event, outputModel: outputModel) inputModel.loginBorderColor.map { loginView.layer.borderColor = $0 } inputModel.passwordBorderColor.map { passwordView.layer.borderColor = $0 } inputModel.loginButtonEnable.map { loginButton.isEnabled = $0 } inputModel.popupErrorMessage.map(showPop) } =64
  33. Получаем модель изменений UI func makeInputModel(event: Event, outputModel: LoginOutputModel?) ->

    LoginInputModel { switch event { case .textFieldTextDidChange: let mapValidToColor: (Bool) -> CGColor? = { $0 ? normalColor : nil } return LoginInputModel(/**/) case .loginDidEndEdit: return LoginInputModel(/**/) case .passwordDidEndEdit: return LoginInputModel(/**/) case .loginPressed: return LoginInputModel(/**/) case .authFailure(let error) where error is AuthError: return LoginInputModel(/**/) case .authFailure: return LoginInputModel(/**/) } } =66
  34. Текст в полях изменился case .textFieldTextDidChange: let mapValidToColor: (Bool) ->

    CGColor? = { $0 ? normalColor : nil } return LoginInputModel( loginBorderColor: outputModel .map { $0.loginIsValid } .flatMap(mapValidToColor), passwordBorderColor: outputModel .map { $0.passwordIsValid } .flatMap(mapValidToColor), loginButtonEnable: outputModel?.passwordIsValid ) =67
  35. Плюсы/минусы ➕ Очевидный поток данных ➕ Более декларативный подход ➕

    Проще отлаживать ➕ Можно покрыть модульными тестами ➖ Обновляется весь UI сразу ➖ Больше кода =68
  36. Итог ▪ Промышленное ФП это не функции, монады, функторы и

    математику. ▪ Чистые функции упрощают код ▪ Функторы и монады помогают концентрироваться на работе с данными ▪ Нужно стараться уменьшать количество изменяемых состояний. =69