Video #6: Functional Setters
Episode: Video #6 Date: Mar 5, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep6-functional-setters

Description
The programs we write can be reduced to transforming data from one form into another. We’re used to transforming this data imperatively, with setters. There’s a strange world of composition hiding here in plain sight, and it has a surprising link to a familiar functional friend.
Video
Cloudflare Stream video ID: b046753ac204072b069e7cb0637983f5 Local file: video_6_functional-setters.mp4 *(download with --video 6)*
References
- Discussions
- Composable Setters
- Functional Swift Conference
- Semantic editor combinators
- 0006-functional-setters
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:06
Today let’s talk about setters! So, the problem is that in our applications we often come across complicated, deeply nested data structures, and we want to be able to modify parts of those structures while keeping everything else fixed. Further, we wanna be able to do it a simple, clean, composable way, where “composable” means that if we have two ways of modify parts of a structure, I should be able to combine them into one thing that modifies both of those parts at the same time. Tuples Let’s look at something simple enough: let pair = (42, "Swift")
— 0:41
What are the ways in which we can transform this tuple? Like what if we wanted to only increment the first component using our old friend incr ? Well we could do: (incr(pair.0), pair.1) // (43, "Swift")
— 1:14
We could also make a function that operates specifically on tuples. We can even make it a bit generic: func incrFirst<A>(_ pair: (Int, A)) -> (Int, A) { return (incr(pair.0), pair.1) } incrFirst(pair) // (43, "Swift")
— 1:55
Neither of these are very reusable in that they are ultimately tied to the incr function. Let’s abstract that away a bit. We’re going to define a super general way of transforming the first component of a tuple, and using our knowledge of higher-order functions , we know that we want the transformation piece to come first because its like configuration. func first<A, B, C>(_ f: @escaping (A) -> C) -> ((A, B)) -> (C, B) { return { pair in return (f(pair.0), pair.1) } } This first function lifts a transformation of (A) -> C up into the world of transformations on tuples, by applying it to the first component. It’s easy enough to apply this function: first(incr)(pair) // (43, "Swift") And we can apply it twice: first(incr)(first(incr)(pair)) // (44, "Swift")
— 3:02
Doesn’t look great, but we can use |> to chain these setters along: pair |> first(incr) |> first(incr) // (44, "Swift")
— 3:17
A cool thing here’s that first was suitably generic that we can even change the type of the first component of the pair: pair |> first(incr) |> first(String.init) // ("43", "Swift")
— 3:26
Here we have incremented the first component, and then converted the Int to a String . We can also define a version of this for the second component of a tuple: func second<A, B, C>(_ f: @escaping (B) -> C) -> ((A, B)) -> (A, C) { return { pair in return (pair.0, f(pair.1)) } }
— 3:56
And now we can apply some additional fun transformations, like transforming the second component! pair |> first(incr) |> first(String.init) |> second { $0 + "!" } ("43", "Swift!")
— 4:21
We could also reach into the second part of the tuple and uppercase it. pair |> first(incr) |> first(String.init) |> second { $0.uppercased() } ("43", "SWIFT")
— 4:28
We can even use our helpers from our episode on higher-order functions. pair |> first(incr) |> first(String.init) |> second(zurry(flip(String.uppercased))) // ("43", "SWIFT")
— 4:43
Now we have a complete data pipeline that can exist without even having a pair present! first(incr) >>> first(String.init) >>> second(zurry(flip(String.uppercased))) // (Int, String) -> (String, String) This is code reuse that was previously hard to see.
— 5:07
In fact, what would this look like with regular ole mutation? Well, we can make a mutable copy and increment the first value. var copyPair = pair copyPair.0 += 1 // 43 We can also mutate the second component, and uppercase it. copyPair.1 = copyPair.1.uppercased()
— 5:38
We still haven’t converted the first integer to a string, though. copyPair.0 = String(copyPair.0) // Cannot assign value of type 'String' to type 'Int'
— 5:47
This doesn’t even work! Our mutable type is fixed, and we have no ability to change parts of it to new types directly. To do so in this world, we’d have to create a whole new copy from scratch.
— 6:08
We are also seeing some algebraic laws hold that are reminiscent of some past episodes. For example, instead of apply first twice with transformations, we could compose the transformations together and then apply first once: pair |> first(incr >>> String.init) // ("43", "Swift")
— 6:26
Say it with me: The composition of the first s is the first s of the composition! This is the second time we have seen this shape. The first was with map , in which we said the composition of maps is the map of the composition. We will see this shape over and over, and soon we will give it a proper name! Nested tuples Now let’s try something more complex. Say we have a nested tuple: let nested = ((1, true), "Swift") Let’s negate the true inside that to be false . We can try piecing together the lil functions we have right now: nested |> first { pair in pair |> second { !$0 } } // ((1, false), "Swift")
— 7:12
It works, but doesn’t look so great. We could drop the named pair argument and use $0 : nested |> first { $0 |> second { !$0 } }
— 7:19
Still not wonderful! There should be a better way to compose these first and second setters. Turns out the answer is, like most things on this series, function composition! There’s mostly only two things you can do with functions: apply to a value, and compose them! We might be tempted to write the following: nested |> (first >>> second) { !$0 } It reads like we’re diving into the first part of the tuple, then diving into the second part of the nested tuple, but this doesn’t compile. Generic parameter 'B' could not be inferred
— 7:45
This does not compile, unfortunately, but we do have a quick compositional fix at hand. Now this composition can be a little bit of a mind trip, so bear with us. The reason it can be a trip is that there’s a bit of a disconnect between how we think of this operation visually and how we perform the transformation. It turns out that we can merely flip the order. nested |> (second >>> first) { !$0 } // ((1, false), "Swift")
— 8:16
It compiles and it produces the value we expect! What’s going on here? Visually we think of this nested transformation as diving into the first layer by using the first function, and then diving into the next layer by using the second function, and then negating the boolean, but in performing the transformation we are actually doing the opposite: we first transform the innermost value, and then we transform the outermost value by plugging in the result we got from the first transformation. So in that sense, this order is more correct.
— 9:00
Composing setter functions corresponds to how you can dive deeper into a nested data structure. Introducing <<<
— 9:05
Well, there’s no reason we can’t define a form of composition that points the other way! In fact, let’s do that and call it backwards composition: precedencegroup BackwardsComposition { associativity: left } infix operator <<<: BackwardsComposition func <<< <A, B, C>( g: @escaping (B) -> C, f: @escaping (A) -> B ) -> (A) -> C { return { x in g(f(x)) } }
— 10:00
And now we update our previous setter: nested |> (first <<< second) { !$0 } ((1, false), "Swift")
— 10:08
This now compiles, executes what we expect it to, and it reads more like how we might expect visually: go into the first component, then go into the second component, then negate.
— 10:18
And even though the backwards arrow is a little familiar, you can think of it as directing a transformation function backwards through the nested structure being piped through.
— 10:31
Now you see why we said that the composition of setters is a little bit of a mind trip: it goes backwards! This seems weird at first, but the fact that this code compiles, and that all the functions involved in the composition are fully generic, is essential universal proof that this is the correct way these setters compose. They couldn’t possibly fit together in any other way.
— 10:57
At the very least we have gained some intuition on why they compose backwards, and over time we’ll just get more comfortable with that fact. And maybe someday we’ll see these shapes somewhere else and we can use our intuition. In fact, you and I have come across this exact thing while building this site. In the Swift code base that powers this site, we have an idea of “middleware”, which is just like a fancy function, and then we have an idea of “middleware transformers”, which are like functions between middleware. And after some time struggling with how they compose we eventually figured out that they compose backwards. And for all the same reasons that these compose backwards.
— 11:23
Before going on, let’s make sure to prove to ourselves that it is worth introducing yet another operator. It satisfies our checklist for pretty much all the reasons that >>> did: ✅ This operator is not currently in Swift. ✅ This operator is used in Haskell and PureScript and has a nice shape to it. ✅ It is solving a universal problem, reverse function composition.
— 12:09
We want <<< and >>> for all the same reasons we want < and > . Sometimes it semantically makes more sense to use the flipped version.
— 12:21
OK, now that we have built up that muscle and intuition for setters composing backwards, let’s flex them a little. Instead of diving into the second element of the nested tuple, we can dive into the first and increment it. nested |> (first <<< first)(incr) // ((2, true), "Swift")
— 12:44
We can keep chain along, negating our true and appending an exclamation mark to our favorite language. nested |> (first <<< first) { $0 + 1 } |> (first <<< second) { !$0 } |> second { $0 + "!" } // ((2, false), "Swift!")
— 13:10
And amazingly we can store this entire transformation in a variable without any mention of the data we are operating on. When the shape doesn’t change, we can use <> to denote the fact that we’re dealing with a single type. let transformation = (first <<< second) { !$0 } <> (first <<< first) { $0 + 1 } <> second { $0 + "!" } // ((Int, Bool), String) -> ((Int, Bool), String)
— 13:27
And wherever we want to use it, we just pipe our value through: nested |> transformation // ((2, false), "Swift!") This shows how modeling setters as simple functions can produce something very composable that makes it easy to reuse code. Arrays There’s another way of adding a layer of nesting, and the functional setters allow us to traverse this layer easily. What if we wanted to add arrays to the mix? (42, ["Swift", "Objective-C"]) How can we dive into the second component of the tuple, and then dive into the elements of the array, and then perform a transformation in there?
— 13:58
To get understanding on how we can do that, let’s take a step back for a moment and look at some shapes. So far all of our setter functions have had this shape: ((A) -> B) -> (S) -> T
— 14:09
In words, we are lifting a transformation on parts (A) -> B up to a transformation on wholes (S) -> T . For example, first and second had these shapes: ((A) -> B) -> ((A, C)) -> (B, C) ((A) -> B) -> ((C, A)) -> (C, B) That is telling us that a transformation on the first component of a tuple can be lifted to a transformation on the whole tuple.
— 14:54
What would this shape look like in arrays? Let’s copy and paste one of these and swap out the tuples for arrays: ((A) -> B) -> ([A]) -> [B]
— 15:11
This looks familiar! It’s our free map function that we defined in a previous episode. It is precisely a function that lifts functions (A) -> B up to the world of arrays ([A]) -> [B] . func map<A, B>(_ f: @escaping (A) -> B) -> ([A]) -> [B] { return { xs in xs.map(f) } }
— 15:28
In some sense, map is a setter-like function. It transforms the parts of the array to get a transformation on the arrays themselves.
— 15:40
Now that we know that map is a setter-like function, just as our first and second functions are, we can compose them all together in all types of ways. We can use map to transform a nested array. (42, ["Swift", "Objective-C"]) |> (second <<< map) { $0 + "!" } // (42, ["Swift!", "Objective-C!"])
— 16:21
Which is neat! We can go really wild and nest this kind of value in an array! [(42, ["Swift", "Objective-C"]), (1729, ["Haskell", "PureScript"])] |> (map <<< second <<< map) { $0 + "!" } // [ // (42, ["Swift!", "Objective-C!"]), // (1729, ["Haskell!", "PureScript!"]) // ]
— 17:32
Jeez! Wild stuff! It’s impressive how we were able to take such a complex structure and effortlessly transform it. What’s the point? OK, we’ve now explored a very mind-bendy way of composing functional setters, we’ve introduced a new operator, and we’ve been able to do some pretty impressive stuff with it, but we don’t know about you, we’re not transforming nested tuples all day. Why is this useful?
— 18:15
Well, tuples were just the simplest structure we could use to transform. It required very little set up to get going. We also deal with nested structures all the time, like structs that have arrays of values, and those values could also have arrays, or optionals, or enums. What we have discussed today is the general framework in which we can understand traversing into nested structures and performing transformations. And just to see how messy this can be without this composable setters let’s recreate that last example in an imperative way. let data = [ (42, ["Swift", "Objective-C"]), (1729, ["Haskell", "PureScript"]) ] data.map { ($0.0, $0.1.map { $0 + "!" }) } It’s short, but messy! We had to remember to re-wrap the tuple to pass the first part of the pair through, and it’s just kind of difficult to figure out what all of this logic is doing: we have to dive in and read from the inside-out rather than rely on <<< to provide a more readable transformation. Worse yet, this logic isn’t reusable or easily extracted!
— 20:13
The next step would be to look at nested structs and see how these ideas apply. However, we want to take it slowly, so we are going to stop here and cover structs in the next episode. Turns out there is an wonderful Swift feature that will help us, and Swift is kind of unique in the programming language world due to this feature. But, no spoilers… until next time! References 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 0006-functional-setters 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 .