Upload
vincent-pradeilles
View
1.232
Download
1
Embed Size (px)
Citation preview
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Advanced functional programmingPragmatic use cases for Monads
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
First-class and higher-order functionslet double: (Int) -> Int = { $0 * 2 }
func apply(value: Int, function: (Int) -> Int) -> Int { return function(value) }
let result = apply(value: 4, function: double) // result == 8
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Some properties• A function is said to be pure if it produces no side-effects and its
return value only depends on its arguments
• An expression is said to be referentially transparent if it can be replaced by its value without altering the program’s behavior
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Some very familiar codevar data: [String]?
// ...
let result = data?.first?.uppercased()
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Some very familiar codevar data: [String]?
// ...
let result = data?.first?.uppercased()
The operator ?. allows us to declare a workflow that will be executed in order, and will prematurely stop if a nil value is encountered
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s try to write the code for ?.extension Optional { func ?.<U>(lhs: Wrapped?, rhs: (Wrapped) -> U) -> U? { switch self { case .some(let value): return .some(rhs(value)) case .none: return nil } } }
Disclaimer : this is a simplified case for methods with no
arguments
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s manipulate an Arraylet data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s manipulate an Arraylet data: [Int] = [0, 1, 2]
let result = data.map { $0 * 2 }.map { $0 * $0 }
Same thing here: the function map allows us to declare a workflow
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
A possible implementation for mapextension Array { func map<U>(_ transform: (Element) -> U) -> [U] { var result: [U] = [] for e in self { result.append(transform(e)) } return result } }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s compose functionslet double: (Int) -> Int = { x in x * 2 } let square: (Int) -> Int = { x in x * x }
infix operator • : AdditionPrecedence
func •<T, U, V>(lhs: (U) -> V, rhs: (T) -> U) -> ((T) -> V) { return { t in lhs(rhs(t)) } }
let result = (double • square)(4) // result == 32
Disclaimer : @escaping attributes have been omitted
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s compose functionslet double: (Int) -> Int = { x in x * 2 } let square: (Int) -> Int = { x in x * x }
infix operator • : AdditionPrecedence
func •<T, U, V>(lhs: (U) -> V, rhs: (T) -> U) -> ((T) -> V) { return { t in lhs(rhs(t)) } }
let result = (double • square)(4) // result == 32
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
We have three similar behaviors, yet backed by very different
implementation
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
What are the common parts?
• They contain value(s) inside a context
• They add new features to existing types
• They provide an interface to transform/map the inner value
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Monad: intuitive definition• Wraps a type inside a context
• Provides a mechanism to create a workflow of transforms
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Minimal Monadstruct Monad<T> { let value: T // additional data static func just(_ value: T) -> Monad<T> { return self.init(value: value) } }
infix operator >>> : AdditionPrecedence
func >>> <U, V>(lhs: Monad<U>, rhs: (U) -> Monad<V>) -> Monad<V> { // specific combination code }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Writer Monad• We have an application that does a lot of numerical calculation
• It’s hard to keep track of how values have been computed
• We would like to have a way to store a value along with a log of all the transformation it went through
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Writer Monadstruct Logged<T> { let value: T let logs: [String] private init(value: T) { self.value = value self.logs = ["initialized with value: \(self.value)"] } static func just(_ value: T) -> Logged<T> { return Logged(value: value) } }
func >>> <U, V>(lhs: Logged<U>, rhs: (U) -> Logged<V>) -> Logged<V> { let computation = rhs(lhs.value) return Logged<V>(value: computation.value, logs: lhs.logs + computation.logs) }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Writer Monadfunc square(_ value: Int) -> Logged<Int> { let result = value * value return Logged(value: result, log: "\(value) was squared, result: \(result)") }
func halve(_ value: Int) -> Logged<Int> { let result = value / 2 return Logged(value: result, log: "\(value) was halved, result: \(result)") }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Writer Monadlet computation = .just(4) >>> square >>> halve
print(computation)
// Logged<Int>(value: 8, logs: ["initialized with value: 4", "4 was squared, result: 16", "16 was halved, result: 8"])
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Now let’s think architectureWe can model the current state of an app with a struct
struct AppState { let userName: String let currentSong: URL? let favoriteSongs: [URL] }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Now let’s think architectureWe can model the action our app emits, in a declarative fashion
protocol Action { }
struct UpdateUserNameAction: Action { let newUserName: String }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Now let’s think architectureWe can react to those actions in a functional way
infix operator >>> : AdditionPrecedence
func >>>(appstate: AppState?, action: Action) -> AppState { var appstate = appstate ?? AppState() switch action { case let newUserNameAction as UpdateUserNameAction: appstate.userName = newUserNameAction.newUserName default: break } return appstate }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Now let’s think architectureAnd finally we put everything together
AppState() >>> UpdateUserNameAction(newUserName: "Vincent")
// AppState(userName: Optional("Vincent"), currentSong: nil, favoriteSongs: [])
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Minimal exampleimport ReSwift
struct AppState: StateType { // ... app state properties here ... }
func appReducer(action: Action, state: AppState?) -> AppState { // ... }
let store = Store( reducer: appReducer, state: AppState(), // You may also start with `nil` middleware: []) // Middlewares are optional
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
A more « real life » example
func appReducer(action: Action, state: AppState?) -> AppState { return AppState( navigationState: navigationReducer(state?.navigationState, action: action), authenticationState: authenticationReducer(state?.authenticationState, action: action), repositories: repositoriesReducer(state?.repositories, action: action), bookmarks: bookmarksReducer(state?.bookmarks, action: action) ) }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
A more « real life » examplefunc authenticationReducer(state: AuthenticationState?, action: Action) -> AuthenticationState { var state = state ?? initialAuthenticationState() switch action { case _ as ReSwiftInit: break case let action as SetOAuthURL: state.oAuthURL = action.oAuthUrl case let action as UpdateLoggedInState: state.loggedInState = action.loggedInState default: break } return state }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
A more « real life » examplelet store: Store<AppState> = Store(reducer: appReducer, state: nil) // in AppDelegate file
class BookmarksViewController: UIViewController, StoreSubscriber { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) store.subscribe(self) { subcription in return subcription.select { state in return state.bookmarks } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated)
store.unsubscribe(self) } func newState(state: StateType?) { // update view } }
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Advantages
• Reducers are pure (stateless) functions, easy to test
• Code review of business logic is easier
• A « demo » mode of the app is easy to implement (UI Testing)
• Deep-linking becomes a trivial use-case
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Drawbacks
• Not a silver bullet, a bit overkill for web-service driven apps
• Requires to create a lot of types (but at least not ObjC types)
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Bibliography• https://en.wikipedia.org/wiki/Monad_(functional_programming)
• https://www.youtube.com/watch?v=ZhuHCtR3xq8 (Brian Beckman: Don't fear the Monad)
• https://academy.realm.io/posts/slug-raheel-ahmad-using-monads-functional-paradigms-in-practice-functors-patterns-swift/ (Using Monads and Other Functional Paradigms in Practice)
• https://github.com/orakaro/Swift-monad-Maybe-Reader-and-Try
• https://academy.realm.io/posts/benji-encz-unidirectional-data-flow-swift/ (Unidirectional Data Flow: Shrinking Massive View Controllers)