KEMBAR78
Advanced functional programing in Swift | PDF
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Advanced functional
programming
Pragmatic use cases for Monads
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Functional Programming
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
First-class and higher-order functions
let 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
Optionals
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Some very familiar code
var data: [String]?
// ...
let result = data?.first?.uppercased()
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Some very familiar code
var 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
Arrays
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s manipulate an Array
let 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 Array
let 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 map
extension 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
Functions
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Let’s compose functions
let 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 functions
let 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 Monad
struct 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
Let’s look at an application
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 Monad
struct 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 Monad
func 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 Monad
let 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 architecture
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Now let’s think architecture
We 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 architecture
We 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 architecture
We 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 architecture
And finally we put everything together
AppState() >>> UpdateUserNameAction(newUserName: "Vincent")
// AppState(userName: Optional("Vincent"), currentSong: nil,
favoriteSongs: [])
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
ReSwift
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
ReSwift
Vincent Pradeilles - CocoaHeads Lyon - 21/09/17
Minimal example
import 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 » example
func 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 » example
let 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
Questions ?
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)

Advanced functional programing in Swift

  • 1.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Advanced functional programming Pragmatic use cases for Monads
  • 2.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Functional Programming
  • 3.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 First-class and higher-order functions let 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
  • 4.
    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
  • 5.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Optionals
  • 6.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Some very familiar code var data: [String]? // ... let result = data?.first?.uppercased()
  • 7.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Some very familiar code var 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
  • 8.
    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
  • 9.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Arrays
  • 10.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Let’s manipulate an Array let data: [Int] = [0, 1, 2] let result = data.map { $0 * 2 }.map { $0 * $0 }
  • 11.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Let’s manipulate an Array let 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
  • 12.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 A possible implementation for map extension Array { func map<U>(_ transform: (Element) -> U) -> [U] { var result: [U] = [] for e in self { result.append(transform(e)) } return result } }
  • 13.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Functions
  • 14.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Let’s compose functions let 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
  • 15.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Let’s compose functions let 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
  • 16.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 We have three similar behaviors, yet backed by very different implementation
  • 17.
    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
  • 18.
    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
  • 19.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Minimal Monad struct 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 }
  • 20.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Let’s look at an application
  • 21.
    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
  • 22.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Writer Monad struct 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) }
  • 23.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Writer Monad func 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)") }
  • 24.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Writer Monad let 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"])
  • 25.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Now let’s think architecture
  • 26.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Now let’s think architecture We can model the current state of an app with a struct struct AppState { let userName: String let currentSong: URL? let favoriteSongs: [URL] }
  • 27.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Now let’s think architecture We can model the action our app emits, in a declarative fashion protocol Action { } struct UpdateUserNameAction: Action { let newUserName: String }
  • 28.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Now let’s think architecture We 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 }
  • 29.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Now let’s think architecture And finally we put everything together AppState() >>> UpdateUserNameAction(newUserName: "Vincent") // AppState(userName: Optional("Vincent"), currentSong: nil, favoriteSongs: [])
  • 30.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 ReSwift
  • 31.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 ReSwift
  • 32.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Minimal example import 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
  • 33.
    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) ) }
  • 34.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 A more « real life » example func 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 }
  • 35.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 A more « real life » example let 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 } }
  • 36.
    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
  • 37.
    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)
  • 38.
    Vincent Pradeilles -CocoaHeads Lyon - 21/09/17 Questions ?
  • 39.
    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)