Setters: Ergonomics & Performance

Episode #15 • May 14, 2018 • Subscriber-Only

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.

Previous episode
Setters: Ergonomics & Performance
Introduction
00:05
Refining things
00:56
Overloading prop
02:39
Double-overloading prop
04:07
Over and set
05:16
Generalizing over and set
13:23
Setter as a type
15:27
Refactoring an earlier example
17:40
Setters and performance
22:34
One more mutable refactor
29:24
What’s the point?
31:34

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Introduction

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.

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.

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in

Exercises

  1. We previously saw that functions (inout A) -> Void and functions (A) -> Void where A: AnyObject can be composed the same way. Write mver, mut, and ^ in terms of AnyObject. Note that there is a specific subclass of WritableKeyPath for reference semantics.

  2. Our episode on UIKit styling was nothing more than setters in disguise! Explore building some of the styling functions we covered using both immutable and mutable setters, specifically how setters compose over sub-typing in Swift, and how setters compose between roots that are reference types, and values that are value types.

  3. We’ve explored <>/concat as single-type composition, but this doesn’t mean we’re limited to a single generic parameter! Write a version of <>/concat that allows for composition of value transformations of the same input and output type. This should allow for prop(\UIEdgeInsets.top) <> prop(\.bottom) as a way of assigning both top and bottom the same value at once.

  4. Define an operator-free version of setters using with and concat from our episode on composition without operators. Define an update function that combines the semantics of with and the variadic convenience of concat for ergonomics.

  5. In the Haskell Lens library, over and set are defined as infix operators %~ and .~. Define these operators and explore what their precedence should be, updating some of our examples to use them. Do these operators tick the boxes?

References

Swift Overture

Brandon Williams & Stephen Celis • Monday 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.

Composable Setters

Stephen Celis • Saturday 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.

Semantic editor combinators

Conal Elliott • Monday 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!

Downloads