Video #15: Setters: Ergonomics & Performance
Episode: Video #15 Date: May 14, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep15-setters-ergonomics-performance

Description
Functional setters can be very powerful, but the way we have defined them so far is not super ergonomic or performant. We will provide a friendlier API to use setters and take advantage of Swift’s value mutation semantics to make setters a viable tool to bring into your code base today.
Video
Cloudflare Stream video ID: 080ef7f714ecba82c2ad1ab9127c6770 Local file: video_15_setters-ergonomics-performance.mp4 *(download with --video 15)*
References
- Discussions
- Swift Overture
- Composable Setters
- Functional Swift Conference
- Semantic editor combinators
- 0015-setters-pt-3
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
We’ve now spent a couple episodes exploring “functional setters”: functions that allow us to build up expressive, immutable data transformations from small units. We’ve explored how they compose together in surprising ways to let us make changes to deeply-nested values: changes that are generally cumbersome to make. And we’ve leveraged a wonderful and unique Swift feature, key paths, to pluck setters out of thin air for properties on our classes and structs.
— 0:31
Setters are an incredibly powerful and broadly useful tool, but the current functions we’ve written have some rough edges when it comes to using them. They’re also not the most performant things in the world: because setters are immutable, they create copies of their values every step of the way. Today we’ll smooth out those rough edges and explore how we can use Swift’s value mutation semantics to make things more performant. Refining things
— 0:56
Let’s start with an example from last time. We have Food , Location , and User structs, and a user value to play with. struct Food { var name: String } struct Location { var name: String } struct User { var favoriteFoods: [Food] var location: Location var name: String } var user = User( favoriteFoods: [Food(name: "Tacos"), Food(name: "Nachos")], location: Location(name: "Brooklyn"), name: "Blob" )
— 1:03
In order to transform these properties, we wrote prop , a function that, given a writable key path, produces a setter function. Without key paths, we’d need to manually write or generate setters for every property that we want to alter, something that may have prevented us from using them in the first place! func prop<Root, Value>(_ kp: WritableKeyPath<Root, Value>) -> (@escaping (Value) -> Value) -> (Root) -> Root { return { update in { root in var copy = root copy[keyPath: kp] = update(copy[keyPath: kp]) return copy } } }
— 1:10
Let’s look at some setters in use. Here we’re piping our user through a couple prop setters: one uppercases their name, and the other relocates them to Los Angeles. user |> (prop(\.name)) { $0.uppercased() } <> (prop(\.location.name)) { _ in "Los Angeles" }
— 1:22
Using prop looks a bit strange in practice because it’s curried: calling prop with a key-path returns another function. We’re wrapping the first application in parens as a kind of party trick to restore trailing closure syntax, but tricks like these are confusing more than anything else. We may come across those extra parentheses and wonder: why are they there? We try to remove them and then puzzle through a confusingly-worded compiler error. prop(\.name) { $0.uppercased() } Extra argument in call
— 1:55
A less confusing way of writing these lines looks like this: user |> prop(\.name)({ $0.uppercased() }) <> prop(\.location.name)({ _ in "Los Angeles" })
— 2:03
But this feels a bit gross. Why are these functions curried? Setter functions need to be curried for composition, and we need composition to make deep changes to our data structures. Consuming setters should be just as nice as composing them, though, so what can we do to eliminate the awkward way we have to call these curried functions?
— 2:21
It might help to consider the two common cases of how we use these setters: we either take existing values and transform them, as we do when we uppercase our user’s name, or we replace the value completely, as we do with their location’s name. Let’s see if we can specialize these two cases with something a bit nicer. Overloading prop
— 2:39
Applying a transformation without those extra parentheses should be as simple as writing an uncurried version of prop . We can write an overload to do just that. func prop<Root, Value>( _ kp: WritableKeyPath<Root, Value>, _ f: @escaping (Value) -> Value ) -> (Root) -> Root { return prop(kp)(f) }
— 3:16
This allows us to update our previous code to something that looks more reasonable. user |> prop(\.name) { $0.uppercased() } <> prop(\.location.name) { _ in "Los Angeles" }
— 3:35
The change is subtle but worth it! It becomes even more noticeable if we pass a named function along. let addCourtesyTitle = { $0 + ", Esq." } user |> prop(\.name, addCourtesyTitle) |> prop(\.name) { $0.uppercased() } <> prop(\.location.name) { _ in "Los Angeles" }
— 3:57
The absence of parentheses make this code easier to read and understand. Double-overloading prop
— 4:07
What about the case where we want to replace our value entirely, like the user’s location name? Right now we’re using a transform function where we ignore the current value and return a constant string. The curly braces, underscore, and in keyword are adding a lot of noise to a very simple operation. So let’s write a function that gets rid of that noise. We can overload prop again, because why not? It’ll take a key path, a value, and produce another Root -transforming function. func prop<Root, Value>( _ kp: WritableKeyPath<Root, Value>, _ value: Value ) -> (Root) -> Root { return prop(kp) { _ in value } } This function simply calls to uncurried prop and hides some of that noise away.
— 4:57
How does this change our pipeline? user |> prop(\.name, addCourtesyTitle) <> prop(\.name) { $0.uppercased() } // <> prop(\.location.name) { _ in "Los Angeles" } <> prop(\.location.name, "Los Angeles")
— 5:12
We’ve hidden away a lot of noise! Over and set
— 5:16
We might imagine that we’re done here, but we’re overlooking a crucial part of our setters machinery, and that’s composing setter functions together in order to change values that are nested deeply in our data structures.
— 5:40
For example, we can compose prop into free map on Array and into prop again to change the names of our user’s favorite foods. user |> prop(\.name, addCourtesyTitle) <> prop(\.name) { $0.uppercased() } <> prop(\.location.name, "Los Angeles") <> (prop(\.favoriteFoods) <<< map <<< prop(\.name)) { $0 + " & Salad" }
— 6:10
This is what made setters so powerful: we could compose them together to create brand new setters that change values located deeply in a data structure.
— 6:20
Now it’s a bit unfortunate that we’re back to wrapping things in parens again. The parens feel necessary here in order to contain the composition, but compared to our other, overloaded prop s, it feels like we’re living in two different worlds: one world that is highly tuned to transforming or setting values, and another that is highly tuned to composing.
— 6:48
But things start to go downhill when we extract this transform function and name it. let healthierOption = { $0 + " & Salad" } user |> prop(\.name, addCourtesyTitle) <> prop(\.name) { $0.uppercased() } <> prop(\.location.name, "Los Angeles") <> (prop(\.favoriteFoods) <<< map <<< prop(\.name))(healthierOption)
— 7:00
We’re back to having that double-parentheses from earlier.
— 7:09
Maybe overloading prop wasn’t the best approach. Maybe prop should live firmly in the world of composition and the creation of setters, while we’ll come up with newly named helpers that are specialized to consume setters. Let’s call our first helper over , which is a term of art that is used in the Haskell community for similar transformations, and we can think of it as mapping over a setter function with a transformation. func over<Root, Value>( _ setter: (@escaping (Value) -> Value) -> (Root) -> Root, _ f: @escaping (Value) -> Value ) -> (Root) -> Root { return setter(f) } The body of this function ended up being plain ole function application, which makes sense given the currying and calling, but was kinda hidden when key paths were also in play.
— 8:30
Let’s also generalize the prop overload that replaced values. We’ll rename it set , another term of art that doesn’t have the baggage of prop being about properties. func set<Root, Value>( _ setter: (@escaping (Value) -> Value) -> (Root) -> Root, _ value: Value ) -> (Root) -> Root { return over(setter) { _ value } }
— 9:10
How do these functions affect our pipeline? user |> prop(\.name, addCourtesyTitle) <> prop(\.name) { $0.uppercased() } <> prop(\.location.name, "Los Angeles") <> over( prop(\.favoriteFoods) <<< map <<< prop(\.name), healthierOption )
— 9:41
This is definitely better, but we’re kind of stuck between two worlds: our prop overload helpers and our more general over and set helpers. Let’s fully convert things over to use over and set . user |> over(prop(\.name), addCourtesyTitle) <> over(prop(\.name)) { $0.uppercased() } <> set(prop(\.location.name), "Los Angeles") <> over( prop(\.favoriteFoods) <<< map <<< prop(\.name), healthierOption )
— 10:36
Alright, this works, we’re avoiding our prop overloads and being consistent, but we’re overloaded with prop everywhere.
— 10:47
In our episode on getters , we wrote a get function that produced a (Root) -> Value function from any key path. This unlocked a whole new world of composition and let us make more expressive calls to map , filter , and other higher-order functions. By the end of the episode, we defined a prefix operator, ^ , as a shorthand for get , and this helped us reduce a lot of noise. ^\User.name // (User) -> String
— 11:15
It seems that prop might benefit from a similar treatment! Let’s alias it using the same operator we used for get . prefix func ^ <Root, Value>(kp: WritableKeyPath<Root, Value>) -> (@escaping (Value) -> Value) -> (Root) -> Root { return prop(kp) }
— 11:36
How does this clean up our composed setters? user |> over(^\.name, addCourtesyTitle) <> over(^\.name) { $0.uppercased() } <> set(^\.location.name, "Los Angeles") <> over(^\.favoriteFoods <<< map <<< ^\.name, healthierOption)
— 12:00
Wow! That did a lot! It’s much easier to get to the substance of those two lines without prop everywhere. Those overloads weren’t really pulling their weight! What we’re left with is much more consistent: key paths are universally prefixed with ^ , and we’re dealing with only two root operations: over and set .
— 12:14
We wrote a new operator function. Do we need to tick the boxes? Well, when we ticked the boxes for ^ on get , we found that maybe it only ticked half of our boxes: we thought it was nice, but not quite as universal as some of our other operators. This operator is kind of a “take it or leave it” operator: if you don’t want to use operators, you still have prop , since ^ is just prop under the hood, but it you don’t mind adopting an operator, the ^ is a nice operator to signal lifting a key path up into the setter world. Generalizing over and set
— 13:23
There’s still one problem with over and set .
— 13:31
In our first episode on setters , we worked with a pair of functions that transformed a tuple’s elements: first and second . ("Hello, world!", 42) |> first { _ in [1, 2, 3] } |> second(String.init) // ([1, 2, 3], "42")
— 13:52
This is pretty succinct, but we should be able to use set to clean up the curly braces and underscore– in noise, and over would make things consistent with our other setters. ("Hello, world!", 42) |> set(first, [1, 2, 3]) |> over(second, String.init) Generic parameter ‘Value’ could not be inferred
— 14:24
Looks like we didn’t generalize enough! Our over and set functions are restricted to Root and Value generic parameters, which prevent either from changing to a different type! We can loosen these constraints by adding a couple more generics. func over<S, T, A, B>( _ setter: (@escaping (A) -> B) -> (S) -> T, _ set: @escaping (A) -> B ) -> (S) -> T { return setter(set) } Alright let’s slow down here. That’s a lot of generic parameters! And “
STAB 15:07
We can also update set . func set<S, T, A, B>( _ setter: (@escaping (A) -> B) -> (S) -> T, _ value: B ) -> (S) -> T { return over(setter) { _ in value } }
STAB 15:21
And with that we’ve fully generalized over and set to work with any setter. Our tuple transformations compile and they read pretty nicely, too! ("Hello, world!", 42) |> set(first, [1, 2, 3]) |> over(second, String.init) // ([1, 2, 3], "42") Setter as a type
STAB 15:27
Let’s take a closer look at the shape our setters have. While we’ve been looking at a lot of setters, we haven’t really unified them. It might be helpful to think of setters as a type themselves. typealias Setter<S, T, A, B> = (@escaping (A) -> B) -> (S) -> T
STAB 16:12
We’ve seen this shape many, many times. It says: if you give me a way of transforming an A to a B , which can be thought of as a part of a larger structure, then I’ll give you a way of transforming an S to a T , which can be thought of as the whole of the structure.
STAB 16:27
We saw this with map on arrays: ((A) -> B) -> ([A]) -> [B] This is a more specific version of the Setter type alias we defined above, where [A] and [B] sub in for S and T as the “whole” structure.
STAB 16:38
We also saw this with map on optionals: ((A) -> B) -> (A?) -> B? Here, A? and B? are the S and T .
STAB 16:44
We even saw this with our first and second functions. ((A) -> B) -> ((A, C)) -> (B, C) Here, we see that the larger tuple structure is the S and T , while the first (A) -> B transform function is responsible for transforming a part of that tuple.
STAB 17:02
All of these setters turn out to have the same, general shape. Now we can think of over and set in terms of this type alias! func over<S, T, A, B>( _ setter: Setter<S, T, A, B>, _ set: @escaping (A) -> B ) -> (S) -> T { return setter(set) } func set<S, T, A, B>( _ setter: Setter<S, T, A, B>, _ value: B ) -> (S) -> T { return over(setter) { _ in value } }
STAB 17:15
We’ve further abstracted here, which is nice, as these two helpers work with setters as values more than anything else. Refactoring an earlier example
STAB 17:40
Now that we have these all-purpose, ergonomic tools under our belt, let’s use em! We can revisit the real-world URLRequest code from last time. let guaranteeHeaders = (prop(\URLRequest.allHTTPHeaderFields)) { $0 ?? [:] } let postJson = guaranteeHeaders <> (prop(\.httpMethod)) { _ in "POST" } <> ( prop(\.allHTTPHeaderFields) <<< map <<< prop(\.["Content-Type"]) ) { _ in "application/json; charset=utf-8" } let gitHubAccept = guaranteeHeaders <> ( prop(\.allHTTPHeaderFields) <<< map <<< prop(\.["Accept"]) ) { _ in "application/vnd.github.v3+json" } let attachAuthorization = { token in guaranteeHeaders <> ( prop(\.allHTTPHeaderFields) <<< map <<< prop(\.["Authorization"]) ) { _ in "Token " + token } } URLRequest(url: URL(string: "https://www.pointfree.co/hello")!) |> attachAuthorization("deadbeef") <> gitHubAccept <> postJson These are a bunch of helper functions that we built using prop . They ensured that we set headers even when the type system made it difficult, and they allowed us to compose a bunch of transformations together in very little code!
STAB 19:24
Let’s see what happens when we use our new helpers. let guaranteeHeaders = over(^\URLRequest.allHTTPHeaderFields) { $0 ?? [:] } let postJson = guaranteeHeaders <> set(^\.httpMethod, "POST") <> set( ^\.allHTTPHeaderFields) <<< map <<< ^\.["Content-Type"], "application/json; charset=utf-8" ) let gitHubAccept = guaranteeHeaders <> set( ^\.allHTTPHeaderFields <<< map <<< ^\.["Accept"], "application/vnd.github.v3+json" ) let attachAuthorization = { token in guaranteeHeaders <> over( ^\.allHTTPHeaderFields <<< map <<< ^\.["Authorization"] ) { _ in "Token " + token } }
STAB 20:44
This is already looking better. We were able to isolate transformations that mapped over values, and transformations that set values to something new.
STAB 20:48
Refactoring can have a bit of snowball effect. Cleaning up code can reveal patterns that were previously obscured, leading to further cleanup! It’s now easier to see the duplication with how we set headers! We’re using a powerful traversal into an optional dictionary of optional values, where we can confidently set new values with low-level combinators! There’s a lot of tricky duplication needed to navigate these types, so why not wrap this logic in a function with a simpler interface? let setHeader = { name, value in guaranteeHeaders <> set(^\.allHTTPHeaderFields <<< map <<< ^\.[name], value) }
STAB 21:29
That was easy! Let’s try it out! let postJson = set(^\.httpMethod, "POST") <> setHeader("Content-Type", "application/json; charset=utf-8") let gitHubAccept = setHeader("Accept", "application/vnd.github.v3+json") let attachAuthorization = { token in setHeader("Authorization", "Token " + token) }
STAB 21:49
This is super short now! We’ve built highly-reusable and readable setters for URLRequest using very little code. And we were able to do so with just a few small functions. No external library needed! Setters and performance
STAB 22:34
The setters we’ve defined so far are important for guaranteeing immutability and modifying deep parts of a structure that would have been cumbersome otherwise. They’re unfortunately not the most performant things in the world. Every setter operation currently creates a whole new copy of the root structure. Every time we call over or set , we’re incurring a performance cost. It’s as if we were calling map on our structure a bunch of times in a row rather than somehow composing this mutation together all at once.
STAB 23:00
Luckily, we’ve seen that Swift has a wonderful story here: inout . So what does an inout setter look like? Let’s take another look at our immutable setter functions. typealias Setter<S, T, A, B> = (@escaping (A) -> B) -> (S) -> T func over<S, T, A, B>( _ setter: Setter<S, T, A, B>, _ set: @escaping (A) -> B ) -> (S) -> T { return setter(set) } func set<S, T, A, B>( _ setter: Setter<S, T, A, B>, _ value: B ) -> (S) -> T { return over(setter) { _ in value } }
STAB 23:06
Let’s try to refactor this to the inout world. We can start by making a mutable variant of our Setter type alias. typealias MutableSetter<S, A> = (@escaping (inout A) -> Void) -> (inout S) -> Void Because functions go from a single type to Void , we lose a couple of generic parameters and our ability to change the shape of our types.
STAB 23:41
Now we can swap Setter out in over and set . func over<S, A>( _ setter: MutableSetter<S, A>, _ set: @escaping (inout A) -> Void ) -> (inout S) -> Void { return setter(set) } func set<S, A>( _ setter: MutableSetter<S, A>, _ value: A ) -> (S) -> S { return over(setter) { $0 = value } }
STAB 24:26
We now have both immutable and mutable versions of over and set , which may be an overload too far. It might be more appropriate to call these functions mver and mut to avoid potential ambiguity. func mver<S, A>( _ setter: MutableSetter<S, A>, _ set: @escaping (inout A) -> Void ) -> (inout S) -> Void { return setter(set) } func mut<S, A>( _ setter: MutableSetter<S, A>, _ value: A ) -> (S) -> T { return over(setter) { $0 = value } }
STAB 24:49
Now let’s take another look at our original example. user |> over(^\.name) { $0.uppercased() } <> set(^\.location.name, "Los Angeles") What’s it look like to use our mutating variants? user |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles")
STAB 25:00
We just swap out the names and change our uppercasing to use assignment! But things still aren’t building. Type of expression is ambiguous Our prop operator is only defined for the immutable setter world. Let’s write a version that uses mutation. prefix func ^ <Root, Value>( _ kp: WritableKeyPath<Root, Value> ) -> (@escaping (inout Value) -> Void) -> (inout Root) -> Void { return { update in { root in update(&root[keyPath: kp]) } } }
STAB 25:44
Alright, we’re further along, but we have another problem. Our user is a let , and our version of |> that works on inout requires an inout input. We could make a copy. var newUser = user newUser |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles")
STAB 26:03
And this updates newUser in place, but it adds noise and this removes a lot of the expressiveness we come to expect when using |> . I think we should reconsider how we originally defined |> .
STAB 26:31
When we first defined |> in terms of inout , we balanced the signature so that it, too, returned Void . func |> <A>(_ a: inout A, _ f: (inout A) -> Void) -> Void { f(&a) } This feels principled, but not practical. We defined |> to be expressive, but that doesn’t hold true here. We should be able to use |> in a single expression and configure some data. We must’ve gotten this wrong. What we really want is a function that takes an A , copies it, and then applies a mutable transformation on that copy before returning it. func |> <A>(_ a: A, _ f: (inout A) -> Void) -> A { var a = a f(&a) return a }
STAB 26:55
Now we get a new, transformed user, and our old use hasn’t been mutated. var newUser = user newUser |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles") Pipe-forward now produces copies rather than mutating a value in-place, which means newUser isn’t getting updated. All we need to do, though, is delete a line. var newUser = user |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles")
STAB 27:04
We can even change var to a let ! let newUser = user |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles")
STAB 27:14
This is pretty cool! We’ve been able to squash two mutable setters into one copy and finally reassign the value to a constant that can’t be mutated!
STAB 27:36
How about our setter that manipulated an array of values? let newUser = user |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles") <> mver(^\.favoriteFoods <<< map <<< ^\.name) { $0 += " & Salad" } Type of expression is ambiguous
STAB 27:54
We have a problem: free map doesn’t compose with the inout world. func map<A, B>(_ f: @escaping (A) -> B) -> ([A]) -> [B] { return { $0.map(f) } }
STAB 28:06
We can get rid of our second generic parameter, B and work squarely within the inout A world. func map<A>( _ f: @escaping (inout A) -> Void ) -> (inout [A]) -> Void { return { for i in $0.indices { f(&$0[i]) } } }
STAB 28:49
Since we’re stuck within a single type, maybe map isn’t an appropriate name. We can call this mutEach , instead. func mutEach<A>( _ f: @escaping (inout A) -> Void ) -> (inout [A]) -> Void { return { for i in $0.indices { f(&$0[i]) } } } Let’s plug it in: let newUser = user |> mver(^\.name) { $0 = $0.uppercased() } <> mut(^\.location.name, "Los Angeles") <> mver(^\.favoriteFoods <<< mutEach <<< ^\.name) { $0 += " & Salad" }
STAB 29:05
It builds! One more mutable refactor
STAB 29:24
What about our URLRequest setters? Those created a fair number of copies because every call to setHeaders also called guaranteeHeaders , so we’re looking at two copies per header! Let’s try to refactor them one more time to use inout setters.
STAB 29:47
We’ll start with guaranteeHeaders . let guaranteeHeaders = mver(^\URLRequest.allHTTPHeaderFields) { $0 = $0 ?? [:] }
STAB 30:07
This one was simple enough: over becomes mver and our block now contains an explicit assignment. Let’s look at something messier: let setHeader = { name, value in guaranteeHeaders <> mut(^\.allHTTPHeaderFields <<< map <<< ^\.[name], value) }
STAB 30:08
Replacing set with mut isn’t gonna cut it, we have another map to deal with. We could define a mutEach equivalent for optionals, but we can take advantage of Swift ergonomics by using optional chaining instead! let setHeader = { name, value in guaranteeHeaders <> { $0.allHTTPHeaderFields?[name] = value } }
STAB 30:47
Every mutable setters distills to an (inout Root) -> Void function, which means we can dive into that world at any time!
STAB 30:49
The other helpers are a cinch. let postJson = mut(^\.httpMethod, "POST") <> setHeader("Content-Type", "application/json; charset=utf-8")
STAB 31:00
We just needed one more mut and everything else just worked. Let’s test it out. URLRequest(url: URL(string: "https://www.pointfree.co/hello")!) |> postJson, <> gitHubAccept <> attachAuthorization("deadbeef")
STAB 31:04
We’re just created a new URLRequest , applied a number of composable mutations to it in an efficient manner, and even assigned it to an immutable let for the remainder of the scope! let request = URLRequest(url: URL(string: "https://www.pointfree.co/hello")!) |> postJson, <> gitHubAccept <> attachAuthorization("deadbeef") What’s the point?
STAB 31:34
Okay, it might be time to ask “what’s the point?” In previous episodes we saw how a disciplined study of setters led to some seriously impressive transformations of our data types. Today’s episode has been entirely about strengthening the ergonomics and value proposition of our previous work. In a sense, this entire episode has been the point. We want people to not only see the power of functional setters, but also remove all barriers that could prevent them from adopting them in their codebases. This means providing a friendlier API than prop for the most common use cases so that it’s clearer at the call site what kind of transformation we’re performing. It also means allowing setters to play nicely with Swift value mutation semantics so that performance sensitive use cases don’t have to worry about creating too many copies.
STAB 32:20
Now, there’s no right choice for whether you should use immutable values or mutable values. Each have tradeoffs. We examined this a bit in our episode on side effects , where we converted some code that dealt with references and functions that mutate them into code that used immutable values and functions that returned all new values instead of modifying them. That change fixed a subtle bug we had, but at the cost of creating copies of values. We then converted that code to the inout style with mutable values, which allowed us to avoid the copies, but re-introduced the bug. The new version had syntax that was documenting where mutation was allowed to happen, which at the very least could help us locate the bug.
STAB 33:12
There’s a very good chance that you won’t even notice the performance gains in preventing these extra copies, so immutable is a good place to start. Still, it’s nice that we have the tools to quickly convert from an immutable world to a mutable world without much work. And there’s even a possibility that some day Swift will be smart enough to optimize away all of the intermediate copies in the immutable version. References Swift Overture Brandon Williams & Stephen Celis • Apr 9, 2018 We open sourced the Overture library to give everyone access to functional compositions, even if you can’t bring operators into your codebase. https://github.com/pointfreeco/swift-overture Composable Setters Stephen Celis • Sep 30, 2017 Stephen spoke about functional setters at the Functional Swift Conference if you’re looking for more material on the topic to reinforce the ideas. https://www.youtube.com/watch?v=I23AC09YnHo Semantic editor combinators Conal Elliott • Nov 24, 2008 Conal Elliott describes the setter composition we explored in this episode from first principles, using Haskell. In Haskell, the backwards composition operator <<< is written simply as a dot . , which means that g . f is the composition of two functions where you apply f first and then g . This means if had a nested value of type ([(A, B)], C) and wanted to create a setter that transform the B part, you would simply write it as first.map.second , and that looks eerily similar to how you would field access in the OOP style! http://conal.net/blog/posts/semantic-editor-combinators Downloads Sample code 0015-setters-pt-3 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .