Monday, April 20, 2026
HomeiOS DevelopmentConsequence builders in Swift - The.Swift.Dev.

Consequence builders in Swift – The.Swift.Dev.

[ad_1]

If you wish to make a end result builder in Swift, this text will allow you to to cope with the most typical circumstances when making a DSL.

Swift

Swift end result builder fundamentals


The end result builder proposal (initially it was known as perform builders) was applied in Swift 5.4. This function permits us to construct up a end result worth utilizing a sequence of parts. At first sight, you may suppose, hey this appears to be like like an array with a sequence of parts, besides the coma in between the objects, however nope, that is utterly totally different. However why is it good for us?


Consequence builder can be utilized to create totally new Area-Particular Languages (DSLs) inside Swift. Making a DSL has many benefits, since DSLs are normally tied to a particular downside, the syntax that you simply use to explain the language may be very light-weight, but highly effective and succesful. Since Swift DSLs are kind protected, it’s a lot safer to make use of one as an alternative of manually concatenate objects. Swift DSLs additionally permits us to make use of fundamental management flows inside these embedded micro-languages. 🤔


Let me offer you an instance: you may write HTML in Swift, you may merely write out all of the tags and glue a bunch of String values collectively, however that would not be so protected, proper?


func buildWebpage(title: String, physique: String) -> String {
    """
    <html>
        <head>
            <title>(title)</title>
        </head>
        <physique>
            <h1>(title)</h1>
            <h1>(physique)</h1>
        </physique>
    </html>
    """
}

let html = buildWebpage(title: "Lorem ipsum", physique: "dolor sit amet")
print(html)


We are able to all agree that that is ugly and the compiler will not allow you to to detect the semantic points in any respect. Now if we exchange the next code with a DSL, we’ll enormously advantage of the Swift compiler options. Swift will give us kind security, so our code might be much less error inclined. A DSL can have many constraints and restrictions that’ll assist others to jot down higher code. In our case the listing of tags goes to be a predefined set of values, so you will not have the ability to present a fallacious tag or miss the closing tag, in different phrases your DSL goes to be syntactically legitimate. In fact you continue to can have logical errors, however that is all the time the case, it doesn’t matter what device you select. 🧠


import SwiftHtml

func buildWebpage(title: String, physique: String) -> String {
    let doc = Doc(.unspecified) {
        Html {
            Head {
                Title(title)
            }
            Physique {
                H1(title)
                P(physique)
            }
        }
    }
    return DocumentRenderer().render(doc)
}


As you may see the snippet above appears to be like far more Swifty and we had been additionally capable of take away the duplicate HTML closing tags from the code. We do not have to jot down the characters in any respect and the compiler can kind verify the whole lot for us, so type-o accidents cannot occur. ✅


Earlier than you suppose that end result builders are simply syntactic sugar over underlying information sorts, I’ve to guarantee you that they’re much more complicated than this. It’s an especially superior and highly effective function that it is best to undoubtedly find out about.


You may create all types of end result builders, for instance I am utilizing them to construct validators, consumer interface parts and structure constraints. In fact SGML (HTML, XML) and CSS can be an amazing use-case, however the listing is countless. Let me present you tips on how to construct a easy end result builder.




Constructing a HTML tree construction


I will present you the way I created my SwiftHtml HTML DSL library, as a result of it was a enjoyable challenge to work with and I’ve discovered loads about it, it is also going to interchange the Leaf/Tau template in my future tasks. The principle thought behind SwiftHtml was that I wished to comply with the HTML specs as intently as attainable. So I’ve created a Node construction to characterize a node contained in the doc tree.


public struct Node {

    public enum `Kind` {
        case commonplace     
        case remark      
        case empty        
        case group        
    }

    public let kind: `Kind`
    public let title: String?
    public let contents: String?

    public init(kind: `Kind` = .commonplace,
                title: String? = nil,
                contents: String? = nil) {
        self.kind = kind
        self.title = title
        self.contents = contents
    }
}


A node has 4 variants outlined by the Kind. A normal node will render as a normal HTML tag utilizing the title and the contents. A remark will solely use the contents and empty tag will not have a closing tag and use the title property as a tag title. Lastly the group node might be used to group collectively a number of nodes, it will not render something, it is only a grouping factor for different tags.


The trick in my answer is that these Node objects solely include the visible illustration of a tag, however I’ve determined to separate the hierarchical relationship from this degree. That is why I really launched a Tag class that may have a number of kids. In my earlier article I confirmed a number of methods to construct a tree construction utilizing Swift, I’ve experimented with all of the attainable options and my ultimate selection was to make use of reference sorts as an alternative of worth sorts. Do not hate me. 😅


open class Tag {

    public var node: Node
    public var kids: [Tag]

    public init(_ node: Node, kids: [Tag] = []) {
        self.node = node
        self.kids = kids
    }

}


Now that is how a Tag object appears to be like like, it is fairly easy. It has an underlying node and a bunch of kids. It’s attainable to increase this tag and supply functionalities for all of the HTML tags, resembling the potential of including frequent attributes and I am additionally capable of create subclasses for the tags.


public ultimate class Html: Tag {

    public init(_ kids: [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "html", contents: nil), kids: kids)
    }
}

public ultimate class Head: Tag {

    public init(_ kids: [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "head", contents: nil), kids: kids)
    }
}

public ultimate class Title: Tag {

    public init(_ contents: String) {
        tremendous.init(.init(kind: .commonplace, title: "title", contents: contents))
    }
}

public ultimate class Physique: Tag {

    public init(_ kids: [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "physique", contents: nil), kids: kids)
    }
}

public ultimate class H1: Tag {

    public init(_ contents: String) {
        tremendous.init(.init(kind: .commonplace, title: "h1", contents: contents))
    }
}

public ultimate class P: Tag {

    public init(_ contents: String) {
        tremendous.init(.init(kind: .commonplace, title: "p", contents: contents))
    }
}


All proper, now we’re capable of initialize our Tag tree, however I warn you, it is going to look very awkward.


func buildWebpage(title: String, physique: String) -> Html {
    Html([
        Head([
            Title(title),
        ]),
        Physique([
            H1(title),
            P(body),
        ]),
    ])
}


It’s nonetheless not attainable to render the tree and the syntax shouldn’t be so eye-catchy. It is time to make issues higher and we should always undoubtedly introduce some end result builders for good.




The anatomy of Swift end result builders

Now that now we have our information construction ready, we should always concentrate on the DSL itself. Earlier than we dive in, I extremely suggest to fastidiously learn the official proposal and watch this WWDC video about end result builders, since each assets are superb. 🤓


Constructing an array of parts


The principle factor that I do not like about our earlier buildWebpage perform is that I’ve to consistently write brackets and comas, in an effort to construct our construction. This may be simply eradicated by introducing a brand new end result builder for the Tag objects. We simply should mark an enum with the @resultBuilder attribute and supply a static buildBlock methodology with the given kind.


@resultBuilder
public enum TagBuilder {
    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }
}


This can enable us to make use of an inventory of parts within our DSL constructing blocks, however earlier than we might use it we even have to alter our particular HTML tag init strategies to reap the benefits of this newly created end result builder. Simply use a closure with the return kind that we wish to use and mark your complete perform argument with the @TagBuilder key phrase.


public ultimate class Html: Tag {
    public init(@TagBuilder _ builder: () -> [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "html", contents: nil), kids: builder())
    }
}

public ultimate class Head: Tag {
    public init(@TagBuilder _ builder: () -> [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "head", contents: nil), kids: builder())
    }
}

public ultimate class Physique: Tag {
    public init(@TagBuilder _ builder: () -> [Tag]) {
        tremendous.init(.init(kind: .commonplace, title: "physique", contents: nil), kids: builder())
    }
}


Now we will refactor the construct webpage methodology since it might probably now use the underlying end result builder to assemble the constructing blocks primarily based on the parts. In the event you check out the introduction part contained in the proposal you may get a greater thought about what occurs below the hood.


func buildWebpage(title: String, physique: String) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            H1(title)
            P(physique)
        }
    }
}

let html = buildWebpage(title: "title", physique: "physique")


Anyway, it is fairly magical how we will rework our complicated array primarily based code into one thing clear and good by benefiting from the Swift compiler. I really like this strategy, however there’s extra.


Optionals and additional construct blocks


If you wish to present if assist inside your DSL you must implement some further strategies inside your end result builder object. Do this code, however it will not compile:


func buildWebpage(title: String, physique: String) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            if title == "magic" {
                H1(title)
                P(physique)
            }
        }
    }
}


The construct an non-obligatory end result with an if assertion now we have to consider what occurs right here. If the title is magic we want to return an array of Tags, in any other case nil. So this could possibly be expressed as a [Tag]? kind however we all the time wish to have a bunch of [Tag] parts, now that is simple.


@resultBuilder
public enum TagBuilder {

    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }

    public static func buildOptional(_ part: [Tag]?) -> [Tag] {
        part ?? []
    }
}


However wait, why is it not working? Nicely, since we return an array of tags, however the outer Physique factor was anticipating Tag parts one after one other, so a [Tag] array will not match our wants there. What can we do about this? Nicely, we will introduce a brand new buildBlock methodology that may rework our [Tag]... values right into a plain Tag array. Let me present you actual this fast.


@resultBuilder
public enum TagBuilder {

    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }
    
    public static func buildBlock(_ parts: [Tag]...) -> [Tag] {
        parts.flatMap { $0 }
    }

    public static func buildOptional(_ part: [Tag]?) -> [Tag] {
        part ?? []
    }
}


func buildWebpage(title: String, physique: String) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique { 
            if title == "magic" { 
                H1("Whats up")
                P("World")
            } 

            
    }
}


I hope it is not too sophisticated, however it’s all about constructing the right return kind for the underlying methodology. We wished to have simply an array of tags, however with the if assist we have ended up with an inventory of tag arrays, that is why now we have to remodel it again to a flattened array of tags with the brand new construct block. If you’d like to try a extra easy instance, it is best to learn this submit. ☺️


If and else assist and both blocks


If blocks can return non-obligatory values, now what about if-else blocks? Nicely, it is fairly an analogous strategy, we simply wish to return both the primary or the second array of tags.


@resultBuilder
public enum TagBuilder {

    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }
    
    public static func buildBlock(_ parts: [Tag]...) -> [Tag] {
        parts.flatMap { $0 }
    }    

    public static func buildOptional(_ part: [Tag]?) -> [Tag] {
        part ?? []
    }

    public static func buildEither(first part: [Tag]) -> [Tag] {
        part
    }

    public static func buildEither(second part: [Tag]) -> [Tag] {
        part
    }
}

func buildWebpage(title: String, physique: String) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            if title == "magic" {
                H1("Whats up")
                P("World")
            }
            else {
                P(physique)
            }
        }
    }
}

let html = buildWebpage(title: "title", physique: "physique")


As you may see now we do not want further constructing blocks, since we have already lined the variadic Tag array concern with the non-obligatory assist. Now it’s attainable to jot down if and else blocks inside our HTML DSL. Appears fairly good thus far, what’s subsequent? 🧐


Enabling for loops and maps by means of expressions


Think about that you’ve got a bunch of paragraphs within the physique that you simply’d like to make use of. Fairly simple, proper? Simply change the physique into an array of strings and use a for loop to remodel them into P tags.


func buildWebpage(title: String, paragraphs: [String]) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            H1(title)
            for merchandise in paragraphs {
                P(merchandise)
            }
        }
    }
}

let html = buildWebpage(title: "title", paragraphs: ["a", "b", "c"])


Not so quick, what is the precise return kind right here and the way can we clear up the issue? In fact the primary impression is that we’re returning a Tag, however in actuality we would like to have the ability to return a number of tags from a for loop, so it is a [Tag], in the long run, it is going to be an array of Tag arrays: [[Tag]].


The buildArray methodology can rework these array of tag arrays into Tag arrays, that is ok to supply for assist, however we nonetheless want another methodology to have the ability to use it correctly. We now have to construct an expression from a single Tag to show it into an array of tags. 🔖


@resultBuilder
public enum TagBuilder {

    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }
    
    public static func buildBlock(_ parts: [Tag]...) -> [Tag] {
        parts.flatMap { $0 }
    }

    public static func buildEither(first part: [Tag]) -> [Tag] {
        part
    }

    public static func buildEither(second part: [Tag]) -> [Tag] {
        part
    }

    public static func buildOptional(_ part: [Tag]?) -> [Tag] {
        part ?? []
    }

    public static func buildExpression(_ expression: Tag) -> [Tag] {
        [expression]
    }

    public static func buildArray(_ parts: [[Tag]]) -> [Tag] {
        parts.flatMap { $0 }
    }
}


This manner our for loop will work. The construct expression methodology may be very highly effective, it allows us to supply varied enter sorts and switch them into the information kind that we really want. I will present you another construct expression instance on this case to assist the map perform on an array of parts. That is the ultimate end result builder:


@resultBuilder
public enum TagBuilder {

    public static func buildBlock(_ parts: Tag...) -> [Tag] {
        parts
    }
    
    public static func buildBlock(_ parts: [Tag]...) -> [Tag] {
        parts.flatMap { $0 }
    }


    public static func buildEither(first part: [Tag]) -> [Tag] {
        part
    }

    public static func buildEither(second part: [Tag]) -> [Tag] {
        part
    }

    public static func buildOptional(_ part: [Tag]?) -> [Tag] {
        part ?? []
    }

    public static func buildExpression(_ expression: Tag) -> [Tag] {
        [expression]
    }

    public static func buildExpression(_ expression: [Tag]) -> [Tag] {
        expression
    }

    public static func buildArray(_ parts: [[Tag]]) -> [Tag] {
        parts.flatMap { $0 }
    }
}


Now we will use maps as an alternative of for loops if we favor purposeful strategies. 😍


func buildWebpage(title: String, paragraphs: [String]) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            H1(title)
            paragraphs.map { P($0) }
        }
    }
}

let html = buildWebpage(title: "title", paragraphs: ["a", "b", "c"])


That is how I used to be capable of create a DSL for my Tag hierarchy. Please be aware that I would had some issues fallacious, this was the very first DSL that I’ve made, however thus far so good, it serves all my wants.




A easy HTML renderer


Earlier than we shut this text I would like to point out you the way I created my HTML doc renderer.


struct Renderer {

    func render(tag: Tag, degree: Int = 0) -> String {
        let indent = 4
        let areas = String(repeating: " ", rely: degree * indent)
        change tag.node.kind {
        case .commonplace:
            return areas + open(tag) + (tag.node.contents ?? "") + renderChildren(tag, degree: degree, areas: areas) + shut(tag)
        case .remark:
            return areas + "<!--" + (tag.node.contents ?? "") + "-->"
        case .empty:
            return areas + open(tag)
        case .group:
            return areas + (tag.node.contents ?? "") + renderChildren(tag, degree: degree, areas: areas)
        }
    }

    non-public func renderChildren(_ tag: Tag, degree: Int, areas: String) -> String {
        var kids = tag.kids.map { render(tag: $0, degree: degree + 1) }.joined(separator: "n")
        if !kids.isEmpty {
            kids = "n" + kids + "n" + areas
        }
        return kids
    }
    
    non-public func open(_ tag: Tag) -> String {
        return "<" + tag.node.title! + ">"
    }
    
    non-public func shut(_ tag: Tag) -> String {
        "</" + tag.node.title! + ">"
    }
}


As you may see it is a fairly easy, but complicated struct. The open and shut strategies are easy, the fascinating half occurs within the render strategies. The very first render perform can render a tag utilizing the node kind. We simply change the kind and return the HTML worth in keeping with it. if the node is a normal or a bunch kind we additionally render the kids utilizing the identical methodology.


In fact the ultimate implementation is a little more complicated, it entails HTML attributes, it helps minification and customized indentation degree, however for academic functions this light-weight model is greater than sufficient. This is the ultimate code snippet to render a HTML construction:


func buildWebpage(title: String, paragraphs: [String]) -> Html {
    Html {
        Head {
            Title(title)
        }
        Physique {
            H1(title)
            paragraphs.map { P($0) }
        }
    }
}

let html = buildWebpage(title: "title", paragraphs: ["a", "b", "c"])
let output = Renderer().render(tag: html)
print(output)


If we evaluate this to our very first string primarily based answer we will say that the distinction is large. Actually talking I used to be afraid of end result builders for a really very long time, I assumed it is simply pointless complexity and we do not actually need them, however hey issues change, and I’ve additionally modified my thoughts about this function. Now I can not stay with out end result builders and I really like the code that I can write through the use of them. I actually hope that this text helped you to know them a bit higher. 🙏






[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments