How I Decoded Function Builders in Swift

Shahrukh Alam
Swift India
Published in
9 min readDec 9, 2020

This is my experience with Function Builders in Swift. How I encountered it, the things I did to find behind the scenes which make a Function Builder & ultimately reach the awesomeness of it.

Courtesy: WallPaperCave

So, without further ado, let’s begin. I was watching the What’s New in Swift WWDC 2019 video a few months back. I loved a lot of things like Implicit Returns, Opaque Result Types & Property Wrappers, but I was blown away by something called Function Builders.

Because it looked foreign, yet felt so Swifty, let’s me write an HTML DSL in pure swift like this:

Credits: WWDC 2019

It felt simply awesome to me for writing a DSL because it brings:

  • Syntactic Sugar: Concise yet Expressive, Clear & Easy to use, basically everything that Swift stands for ❤️
  • Composition: Encapsulates Hierarchal Heterogeneous Data Structures
  • Declarative Style
  • Power of Swift: Compiler Type Check & Safety, Auto-Complete & Syntax Highlight
  • No more Context Switching

After a few days, I got the chance to write an App as a part of an interview process, completely in SwiftUI which uses the power of Function Builder extensively. Then, I realized that it’s not only awesome, but it’s a killer 💀too.

Credits: WWDC 2019

Now that I was completely blown away by it, I was excited to know behind the scenes that make a Function Builder. In the same WWDC video Anna Zaks from Swift Team nicely explains how a compiler transforms a simple looking closure into a neat-meat code:

Credits: WWDC 2019

But, I was looking for more, how compiler actually does that. So, I dived into Swift Evolution Github Page:

Swift Evolution Github Page, Credits: WWDC 2019

and found the Initial Draft of the Function Builders proposal & the corresponding PR. But, sadly most of the code was compiler level which I don’t understand. If you do, then it’s definitely a gold mine.
But I did find function_builder.swift which creates a function builder for Tuple which I had seen earlier on the documentation when I was working on SwiftUI.

Courtesy: Function Builder Pull Request

So, now I was left with only two options: SwiftUI Documentation & Tutorial which uses the power of Function Builders extensively. Even though Tutorial is great, it doesn’t talk about how it works behind the scenes. And the Documentation was overwhelming.

So, I decided to try my hands on it on Playground & follow along with the documentation, as an Odiya (the native language of Odisha, an Indian State) saying goes:

Nije namale, jama darshana nahin
(which loosely translates to: “One won’t get to see Yama (the lord of death and justice) until own death”)

Side Note: You can Access documentation without a network connection, Xcode -> Help -> Developer Documentation:

Looking at the Documentation of View, I tried to recreate View (basic building block of SwiftUI Views) Protocol:

Basic implementation of SwiftUI View Protocol

Here comes ViewBuilder into the picture, which is a Function Builder for SwiftUI Views.

Documentation of ViewBuilder was a little too much for me. So, before writing the pseudo-code, I wanted to check if it’s real & if possible gather some extra information from Playground:

SwiftUI View Type Checking

From this simple experiment, I gathered a few extra information:

  • VStack takes a single closure, which is pretty similar to any other swift closure.
  • If we don’t provide any view to it, it takes the form of an EmptyView.
  • If we provide a single view to it, it takes the form of that View (in our case Text)
  • If we provide multiple views to it, it takes the form of a TupleView & stores the views in its value.
  • VStack stores things in a tree of type VariadicView.Tree with self as the root and the final form of closure containing the views as it’s content. This is probably the reason how SwiftUI can apply a modifier (say background color) from parent to all of its children.

With that in mind, I tried filling pseudo-code for ViewBuilder:

With that in place, I wanted to repeat the same experiment I did for SwiftUI VStack, but this time with our VStackAlias & our implementation of EmptyView & TupleView:

and wait for it… the results were pretty similar. Now, I couldn’t agree more with the findings of the SwiftUI playground experiment I did earlier:

Function Builder Mapping

Based on the number of views passed to the closure & their type, the compiler smartly synthesizes code to correctly map the closure to the right build block.

Function Builder compiler code synthesis

So basically, each of the views passed to the VStackAlias closure is effectively an expression synthesized by the compiler behind the hood.
And all this is possible because of the special custom attribute @_functionBuilder which tells the compiler to auto-synthesize the closure block by looking at the definition of different build blocks in the Function Builder.

Now that I had a fair bit of idea, I wanted to implement a few use-cases of Function Builder.

But before that, here is an excerpt for you from the function-builder methods currently proposed:
In the following descriptions, Expression stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result), Component stands for any type that is acceptable for a partial or combined result to have, and Return stands for any type that is acceptable to be ultimately returned by the transformed function.

  • buildExpression(_ expression: Expression) -> Component is used to lift the results of expression-statements into the Component internal currency type. It is only necessary if the DSL wants to either (1) distinguish Expression types from Component types or (2) provide contextual type information for statement-expressions.
  • buildBlock(_ components: Component...) -> Component is used to build combined results for most statement blocks.
  • buildFunction(_ components: Component...) -> Return is used to build combined results for top-level function bodies. It is only necessary if the DSL wants to distinguish Component types from Return types, e.g. if it wants builders to internally traffic in some type that it doesn't really want to expose to clients. If it isn't declared, buildBlock will be used instead.
  • buildDo(_ components: Component...) -> Component is used to build combined results for do statement bodies, if special treatment is wanted for them. If it isn't declared, buildBlock will be used instead.
  • buildOptional(_ component: Component?) -> Component is used to build a partial result in an enclosing block from the result of an optionally-executed sub-block. If it isn't declared, optionally-executed sub-blocks are ill-formed.
  • buildEither(first: Component) -> Component and buildEither(second: Component) -> Component are used to build partial results in an enclosing block from the result of either of two (or more, via a tree) optionally-executed sub-blocks. If they aren't both declared, the alternatives will instead be flattened and handled with buildOptional; the either-tree approach may be preferable for some DSLs.

Examples:

1. Array:

I thought I would start small, something as trivial as an Array.

So basically, I wanted to check if I can write [7, 8, 6] as something like this:

This might look silly to try, but trust me it helped my understanding for the better examples. Okay, Let’s see how I achieved this step by step by a Function Builder.

I started with the buildBlock for the combined result from the partial results:

Next, extended an init on Array to use the ArrayBuilder we just created:

That’s it, now we can initialize an Array using the new ArrayBuilder & this is how it looks:

Let’s talk about the intArray (same goes for stringArray), the Swift compiler does the following steps:

// 1 (as marked on the code)
sees the first value 7 in the closure, stores it in a var a

// 2
sees the second value 8 in the closure, stores it in a var b

// 3
sees the third value 6 in the closure, stores it in a var c

// 4
invokes build block with a, b & c like this: ArrayBuilder.buildBlock(a, b, c)

Then we pass the compiler synthesized closure from (4) to the init of Array; upon executing the closure, it returns [a, b, c] or [7, 8, 6]; which is then assigned to self.

Not tricky at all, right! 🤡
You are awesome 😎, let’s look at something better now.

2. NSAttributedString

First Let’s see how we used to write for Attributed Strings:

This will give us the below output on Playground’s Live View:

This is good, but my final goal was much better:

I started with the buildBlock for the combined result from the partial results:

Next, added buildExpression for what types of expressions the builder supports. In our example, we will accept strings, and lift them to attributed substrings:

Next, added support for modifying attributes with an extension on NSAttributedString:

Last, but best, added a convenience init on NSAttributedString to use the AttributedStringBuilder we just created:

That’s it, now I can initialize NSAttributedString using the new AttributedStringBuilder & this is how it looks:

The Swift compiler translates the above code into the following AttributedStringBuilder method calls:

  1. buildExpression() with the NSAttributedString argument.
  2. buildExpression() with the String argument.
  3. After all partial results have been built, the method buildBlock() is invoked with all intermediate NSAttributedStrings as arguments.

Executing the Playground, gave us the same output as before:

3. AlertViewController:

First Let’s see how we used to write for AlertViewController:

This will give us the below output on Playground’s Live View:

This is good, but using a function builder approach (like we did in the previous example for NSAttributedString) is much better:

4. UIStackView:

We won’t be seeing how we used to write it as we all know, use & love StackView.
So, let’s directly jump onto the Function Builder implementation:

This gives us the below output on Live View:

Even though this example is pretty small, it’s good enough to demonstrate how the function builder approach embraces the in-place context & the truly hierarchical nature of StackView.

4. HTML DSL:
This is something I am currently working on. It’s a rather extensive example, if you have a fair bit of idea on SwiftUI & Function Builder (I hope you will have now 😛), do check out the code on Github.

Conclusion:

In this article, we discussed Function Builder in length. How Apple has brought the power of Swift & DSL together.

I think Swift as a language is getting more & more powerful, elegant & easy to use. We have the right amount of tools to create something beautiful not only on DSL but also on the Server-Side. Now, it’s on us, the developers to take it forward to a completely different level.

I hope you enjoyed the article & learned behind the scenes of Function Builders. Do check out the complete playground on Github.

Happy Coding & Sharing 😍
Cheers 🍺
Shahrukh Alam

--

--