Blog

Defunctionalization

In recent years, the reducer pattern has become more popular in Swift. We see it in UIKit apps, and now in SwiftUI apps.

Consider a simple counter in SwiftUI:

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack {
            Text("You clicked \(count) times")
            Button(action: {
                self.count += 1
            }, label: { Text("Click Me") })
            Button(action: {
                self.count = 0
            }, label: { Text("Reset") })
        }
    }
}

For a small program like the one above, the state and its mutations are easy to grasp. There are two mutations: add one to the state, and reset the state to zero. As the size of your program grows, and mutations proliferate through your views, reducer-based refactoring becomes more appealing.

In a reducer-based style, you look at all the different mutations in your program, and create an enum with one case per mutation. For example, refactoring the program above, we could write the following enum:

enum Action {
    case add(Int)
    case reset
}

The second part to a reducer is the “reduce” function (the name is a bit confusing, as it doesn’t have much to do with reduce in the Swift Standard Library). We’ll write this as a mutating function apply on the state:

extension Int {
    mutating func apply(_ action: Action) {
        switch action {
        case .add(let number): self += number
        case .reset: self = 0
        }
    }
}

The technique of taking a program and replacing higher-order functions, such as the button’s actions, with enum cases is called defunctionalization, a term coined by John Reynolds, the inventor of the technique, in his 1972 paper Definitional interpreters for higher-order programming languages.

Interestingly, we can use a similar pattern for something very different: representing links in a web application. In our Swift Talk backend, we have a file called Routes.swift which contains a Route enum. We have a function to turn a Route value into a regular link (for example, Route.collection("swiftui") becomes /collections/swiftui), as well as a function that tries to turn a link into a Route value. Instead of an apply function like above, we have a function called interpret which takes a Route, executes any relevant mutations, and renders the resulting HTML.

This pattern—a Route enum combined with the interpret function—is very similar to the reducer above; it is another type of defunctionalized program. In the case of a web server, its utility doesn’t just lie in taming complexity, it makes it possible to describe continuations in a type-safe way (in this case, as an enum case with associated values, rather than a stringly typed link).

Defunctionalizing a program is an almost mechanical task:

  • Create an Action enum (or Route, or whatever is a good name for your domain).
  • Create an apply function that switches over the enum.
  • For each function that you want to defunctionalize, add a case to the enum, and replace the function with a call to apply. If there are any free variables, add associated values to the enum case. Finally, move the original code into the apply function.

While defunctionalization can be done automatically by a compiler, we typically defunctionalize only part of a program, such as the actions in a GUI, or the web server routes.

Defunctionalization isn’t limited to routes and UI actions:

  • In the Swift Talk backend, we have a ScheduledTask enum that represents a task to be executed at a later time. This enum value is stored in the database along with the scheduled date, and the task gets executed when it’s due.
  • Danvy and Nielsen, in their 2001 paper Defunctionalization at Work, show that we can use defunctionalization to transform continuation based programs into stack-based programs. For example, they turn a functional parser into a state-based parser, much as you would write in C.

We use this technique throughout our Swift Talk episodes, often without making it explicit. As an exception, Episode 62 discusses the reducer pattern in application, using it to improve the testability of a typical view controller.

To learn more about our Swift Talk backend, our introduction episode is free to watch. We recently removed most of our dependencies, including the entire Javascript build stack. The backend is open-source, and you can read the source code on GitHub.

Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts