The concept of functional Lens has become pretty popular in functional programming circles, and there are already good contributions for applying lenses to other, traditionally imperative/OO contexts. I’d like to offer a more in depth view on why lenses can be useful in Swift, and also talk about an associated concept called Prism. Let’s consider some practical problems and confront an idiomatic/imperative approach to the one based on lenses and prisms.
Lenses and Prisms in Swift - Elviro Rocca - Codemotion Rome 2018
1. Lenses and Prisms in Swift
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/
3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
3. Lenses and Prisms
→ encapsulate a relationship between a
data structure and its parts
→ simple to define
→ composition tools
→ build powerful abstractions
4.
5.
6.
7. struct Lens<Whole,Part> {
let get: (Whole) -> Part
let set: (Part) -> (Whole) -> Whole
}
struct Prism<Whole,Part> {
let tryGet: (Whole) -> Part?
let inject: (Part) -> Whole
}
8. struct Lens<Whole,Part> {
let get: (Whole) -> Part
let set: (Part) -> (Whole) -> Whole
}
struct Prism<Whole,Part> {
let tryGet: (Whole) -> Part?
let inject: (Part) -> Whole
}
9. struct Lens<Whole,Part> {
let get: (Whole) -> Part
let set: (Part) -> (Whole) -> Whole
}
struct Prism<Whole,Part> {
let tryGet: (Whole) -> Part?
let inject: (Part) -> Whole
}
10. struct Lens<Whole,Part> {
let get: (Whole) -> Part
let set: (Part) -> (Whole) -> Whole
}
struct Prism<Whole,Part> {
let tryGet: (Whole) -> Part?
let inject: (Part) -> Whole
}
20. struct LoginPage {
var title: String
var credendials: CredentialBox
var buttonState: ViewState<Button>
}
struct CredentialBox {
var usernameField: TextField
var passwordField: TextField
}
struct TextField {
var text: String
var placeholder: String?
var secureText: Bool
}
struct Button {
var title: String
var enabled: Bool
}
21. struct LoginPage {
var title: String
var credendials: CredentialBox
var buttonState: ViewState<Button>
}
struct CredentialBox {
var usernameField: TextField
var passwordField: TextField
}
struct TextField {
var text: String
var placeholder: String?
var secureText: Bool
}
struct Button {
var title: String
var enabled: Bool
}
22. extension CredentialBox {
enum lens {
static var usernameField: Lens<CredentialBox,TextField> {
return Lens<CredentialBox,TextField>.init(
get: { whole in
whole.usernameField
},
set: { part in
{ whole in
var mutableWhole = whole
mutableWhole.usernameField = part
return mutableWhole
}
}
)
}
}
}
23. extension ViewState {
enum prism {
static var processing: Prism<ViewState,String> {
return .init(
tryGet: { whole in
guard case .processing(let message) = whole else {
return nil
}
return message
},
inject: { part in
.processing(part)
}
)
}
}
}
24. Manipulate the model
→ a previous model exists
→ set a greeting title
→ set a stored username
→ button initially not enabled
29. let titleLens = LoginPage.lens.title
let usernameTextLens = LoginPage.lens.credentials
.compose(CredentialBox.lens.usernameField)
.compose(TextField.lens.text)
let buttonStateLens = LoginPage.lens.buttonState
30. let newModel = titleLens.set(initialState.title)
(usernameTextLens.set(initialState.username)
(buttonStateLens.set(initialState.buttonState)
(oldModel)))
41. let onProcessing: ((String) -> String) -> (LoginPage) -> LoginPage
onProcessing = modifyProcessingMessage >>> modifyLoginPage
let newModel = onProcessing(advanceProcessingMessage)(oldModel)
42. Let's recap
→ separated logic for accessing and mutating data;
→ defined simple, composable atomic objects;
→ built more complex objects just by combination;
→ transformed data with multiple, independent
pieces;
→ types are the only interface;
→ nothing will break if types don't change;
→ are we sure?
45. Laws
→ required when building large systems
from small pieces
→ we need to trust the pieces
→ they are implicit in tests
→ let's define them explicitly for Lenses
and Prisms
46. Laws: Lens
→ getSet: the whole stays the same
→ setGet: the part stays the same
→ setSet: set is idempotent
49. Do we need to test ALL lenses and
prisms?
→ trivial lenses have always the same
structure;
→ if written by hand, tests can be useful;
→ lots of boilerplate;
→ a perfect fit for code generation;
→ Sourcery
58. struct Affine<Whole,Part> {
let tryGet: (Whole) -> Part?
let trySet: (Part) -> (Whole) -> Whole?
}
/// laws are similar to lens'
/// es. Affine to an index of an Array