Alejandro Martinez

06 April, 2019Swift🕐 3min read

Generalising Swift string interpolation

In the first article of this series we made a safe string type that was safe to use but with a cumbersome API, and on the second article we made that API much nicer to Swift 5 interpolation. On this post we will generalise that conformance to make it easy to create new safe languages.

 Making XML a safe string

As an example let's use XML.

struct XML: Language {
    let name = "XML"
    
    var value: String = ""
    
    mutating func appendFragment(_ fragment: String) {
        value.append(fragment)
    }
    
    mutating func appendText(_ text: String) {
        value.append(escapeXML(xml: text))
    }
    
    private func escapeXML(xml: String) -> String {
        return CFXMLCreateStringByEscapingEntities(nil, xml as CFString, nil) as String
    }
}

Don't take this implementation as production ready as it's just for example purposes.

Now we can create a safe version of an XML string combining fragments of unsafe text that will be properly escaped.

var xml = XML()
xml.appendFragment("<b>")
xml.appendText("danger & warnings")
xml.appendFragment("</b>")
xml.value // "<b>danger &amp; warnings</b>"

Protocols to the rescue

Thanks to powerful Swift protocols we can easily generalise the String interpolation conformance from the previous posts directly into the Language protocol.

protocol InterpolableLanguage: Language, ExpressibleByStringLiteral, StringInterpolationProtocol, ExpressibleByStringInterpolation {
    init()
}

ExpressibleByStringInterpolation already brings ExpressibleByStringLiteral so that conformance is redundant. Is just displayed here for clarity.

I decided here to create a new protocol InterpolableLanguage because we can't extend the Language protocol and because we will need an initialiser to be available; I didn't want to pollute the basic Language protocol.

There are many other design possibilities. From creating a generic type SafeString<T: Language> like the original post, or even using more protocols and extensions. It would be an interesting design space for anybody interested in making a library with these concepts.

Now let's implement all the conformances in extensions to that protocol. This code looks identical at the one from the previous post, except now is implemented in the protocol

extension InterpolableLanguage { // ExpressibleByStringLiteral
    init(stringLiteral value: String) {
        self.init()
        self.appendText(value)
    }
}
extension InterpolableLanguage { // StringInterpolationProtocol
    init(literalCapacity: Int, interpolationCount: Int) {
        self.init()
    }
    mutating func appendLiteral(_ literal: String) {
        self.appendFragment(literal)
    }
    mutating func appendInterpolation(unsafe text: String) {
        self.appendText(text)
    }
    mutating func appendInterpolation(safe text: String) {
        self.appendFragment(text)
    }
}
extension InterpolableLanguage { // ExpressibleByStringInterpolation
    init(stringInterpolation: Self) {
        self.init()
        appendFragment(stringInterpolation.value)
    }
}

This is one of the beauties of Swift. Going from code that was specifically written for a type and generalising it to a lot of types thanks to protocol extensions.

Now we can make our new XML type get all these features with a single line:

extension XML: InterpolableLanguage {}

And with only that line we get all the interpolation capabilities:

let xml: XML = "<b>\(unsafe: "danger & warnings")</b>"
xml.value // "<b>danger &amp; warnings</b>"

And thanks to the safe and unsafe overloads we can combine XML strings easily:

let startP = "<p>"
let endP = "</p>"
let final: XML = """
\(safe: startP)
\(unsafe: "danger & warnings")
\(safe: endP)
"""

We can even use Swift multiline strings with it!

Conclusion

In this post we've seen how Swift makes it easy to get a powerful feature implemented for a specific type and generalise it to many more.

Now we can create safe string languages easily and without much effort.

In the next article we will reimplement the original example from years ago with this new and updated system.

👉🏻 If you have any feedback you can reach me at alexito4

RSS

Alejandro Martinez
alexito4@gmail.com

Buka pintu