Learning Swift: Convertibles

Note: this post is part of a series about the Swift programming language, introduced at WWDC 2014. I’m no more experienced in Swift than anyone else outside Apple, but I learn best by coding and talking through a problem. If there’s a better way to approach some of these topics, get in touch on Twitter!

As the Swift language matures, most of the standard library has begun to settle into a stable shape. This includes a group of protocols that collectively define convertibles.

Unfortunately, we’re not talking about the cars: a convertible in Swift is, generally speaking, a data type that can be implicitly constructed from a literal. By conforming to one of the protocols in the standard library, your types can provide an extra level of convenience to you or anyone else using your code.

The Inner Workings of a Convertible

Let’s begin by breaking down our earlier definition to discuss exactly what a convertible is:

  • Convertibles are constructed from literals: in code, you’ll type a value directly inline – such as 3, "foo", or true. Other variables, even if they’re of the same primitive types, don’t qualify for literal conversion.
  • Convertibles are implicitly constructed: to convert from the literal’s native type to your convertible type, you just have to convince Swift of the final type (usually through type inference). You don’t need to explicitly call any constructors, and in most cases, you can avoid explicit casts as well.

The standard library provides these behaviors by defining a different convertible protocol for each kind of literal the language recognizes. Often, your types will conform to the basic Nil, Boolean, Integer, Float, or String convertible protocols. In more complex situations, you might also support collections with the Array and Dictionary literal convertible protocols. Finally, you could get in-depth with text by supporting the UnicodeScalar or ExtendedGraphemeCluster protocols.

All of these protcols have a fairly common pattern: they each define a single initializer that takes a value of the kind named in the protocol. For example, IntegerLiteralConvertible looks like this:

/// Conforming types can be initialized with integer literals
protocol IntegerLiteralConvertible {
    typealias IntegerLiteralType

    /// Create an instance initialized to `value`.
    init(integerLiteralValue value: IntegerLiteralType)
}

By conforming to one of these protocols, your data type is declaring that it can be initialized with whatever literal the developer writes. The actual logic of translating that literal value to an instance of your type is left up to you to implement inside the initializer.

Simple Convertibles: Complex Numbers

A great use case for convertibles is the mathematical concept of a complex number. Frequently referred to as “imaginary numbers,” a complex number has two parts:

  • A real component, which is just a regular number, and
  • An imaginary component, which is a regular number times the “imaginary unit” i (the square root of -1).

By convention, we generally write a complex number as a + bi, where a is the real component and b is the imaginary component. The following, then, can all be treated as complex numbers:

  • 2 + 3i
  • 4 (which has an implicit imaginary component of 0i)
  • 7i (which has an implicit real component of 0)

For this concept, we can define a new data type in Swift. Firing up a playground, we can write:

struct Complex {
    var real: Double
    var imaginary: Double
}

(I’ll use Doubles in this example for broader numeric support; if you only need to work with integers, you could conceivably define Complex with Int members.)

At this point, we can construct an instance of Complex using its inferred initializer (which is given to all structs without any explicitly declared initializers). We’d write something like:

let a = Complex(real: 1.0, imaginary: 2.0)

What happens, though, if we want a Complex with only a real component? We could certainly define it passing 0.0 for the imaginary argument. However, using convertibles, we can make that sort of real-only declaration much shorter. Let’s implement FloatLiteralConvertible for Complex:

extension Complex: FloatLiteralConvertible {
    init(floatLiteral value: FloatLiteralType) {
        self.real = value
        self.imaginary = 0.0
    }
}

At this point, we can construct a Complex from any Float literal, so long as we can convince Swift that the result type is supposed to be Complex (and not a bare Float):

let b: Complex = 2.0

Notice how we aren’t explicitly calling any constructor on Complex. Instead, we’re taking advantage of Swift’s type coercion and the FloatLiteralConvertible protocol to implicitly call the initializer we just defined. Once evaluated, this expression is equivalent to assigning Complex(real: 2.0, imaginary: 0.0) to the variable b.

Crossing Type Boundaries

Implicit conversion to Complex from Float is cool, but was pretty straightforward – the Complex type already had float-like members, since we defined it using Double. We can get a little fancier by also providing support for implicit Int conversion:

extension Complex: IntegerLiteralConvertible {
    init(integerLiteral value: IntegerLiteralType) {
        self.real = Double(value)
        self.imaginary = 0.0
    }
}

This implementation looks really similar to our FloatLiteralConvertible conformance, with one minor tweak. Since the value being passed in can no longer implicitly be cast to Double, we need to construct a Double value when setting self.real. Now, like above, we can make a Complex using a plain integer literal:

let c: Complex = 2

Note the lack of .0 after the number – this literal is an Int rather than a Float, and so is using our newly defined protocol conformance.

These Types Have the Longest Names

Let’s continue with our implicit conversions, but leave the realm of number literals. Swift provides a StringLiteralConvertible that we can attempt to implement, parsing complex expressions of the form “a+bi” when constructing a Complex.

We can sketch out a quick, very limited parser using an NSScanner inside the required initializer:

extension Complex: StringLiteralConvertible {
    init(stringLiteral value: StringLiteralType) {
        let scanner: NSScanner = NSScanner(string: String(value))
        self.real = 0.0
        self.imaginary = 0.0
        
        if scanner.scanDouble(&self.real) == false {
            return
        }
        
        if scanner.scanString("+", intoString: nil) == false {
            return
        }
        
        if scanner.scanDouble(&self.imaginary) == false {
            return
        }
        
        if scanner.scanString("i", intoString: nil) == false {
            return
        }
    }
}

At this point, we should stop and discuss the effect and limitations of this sort of implementation. While it serves to demonstrate String conversion, production code probably wouldn’t go this route for a variety of reasons.

First off, conforming to StringLiteralConvertible says we can convert any string literal to a Complex instance, even those that look nothing like a complex number. This implementation just falls back to returning something of the form 0+0i in the worst cases, because it can’t do anything better – the initializer in the protocol isn’t failable, so it can’t return nil.

In addition, the use of NSScanner here is fairly primitive. Though NSScanner can be part of a very powerful parsing algorithm, the way it’s used here is quite brittle: using the shorthand “3-4i”, for example, would produce a Complex with an imaginary part of 0 instead of the expected 4. (We’d need to write “3+-4i” to get the expected results.)

Finally, in Xcode 6.1, this code doesn’t even compile! Depending on some particulars, we see one of a few confusing compiler error messages, usually revolving around UnicodeScalarLiteralConvertible or ExtendedGraphemeClusterLiteralConvertible – truly some of the longest protocol names we’ve encountered yet.

These errors come from the fact that StringLiteralConvertible conforms to another convertible protocol, largely as a result of how Strings are built in Swift:

  • A String is a sequence of “Unicode extended grapheme clusters,” which the standard library simply calls “a unit of text that is meaningful to most humans.”
  • An extended grapheme cluster, in turn, is composed of one or more Unicode scalar values – single code points in the Unicode character space.

This means that any type we want to be String literal convertible must also be able to convert from grapheme clusters or Unicode scalars, as indicated by StringLiteralConvertible’s conformance. Thankfully, we can cheat a little bit and just redirect those required initializers over to our String-based initializer, using some type aliases along the way:

extension Complex: StringLiteralConvertible {
    typealias UnicodeScalarLiteralType = StringLiteralType
    init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
        self.init(stringLiteral: value)
    }
    
    typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
    init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
        self.init(stringLiteral: value)
    }

    // the existing String-based initializer from above remains here
}

Now, this extension should compile and we can start translating some strings into Complex instances:

let d: Complex = "1+2i" // gives 1+2i
let e: Complex = "2.0+1.0i" // gives 2+1i
let f: Complex = "3+-4i" // gives 3-4i
let g: Complex = "1 + 2 i" // gives 1+2i

…and we can see what happens when that translation fails:

let h: Complex = "3-4i" // gives 3+0i
let i: Complex = "foo" // gives 0+0i
let j: Complex = "4ty-two" // gives 4+0i

Still, this could be a useful time-saver, in our contrived little world of complex numbers.

In Swift Libraries

One of the best things about the various convertible protocols is how the Swift language itself uses them. Rather than simply expose this behavior for third-party types, Swift uses convertibles for some nifty tricks.

For example, Swift has a Selector type, used when interoperating with Objective-C code that passes around SEL variables. The fastest way to construct a Selector instance knowing the name of an Objective-C selector is, in fact, to use a String literal conversion:

let s: Selector = "hashValue"

Much like our Complex example above, the Selector type defines all the initializers required of String, grapheme cluster, and Unicode scalar convertibles. It also exposes an explicit constructor that takes a String argument, in the event that you’re interpreting a non-literal as a Selector.

Convertibles even worm their way into that most fundamental of Swift concepts, the Optional. As we know, optional types in Swift can either have a “real” value, or can be nil. To ease the declaration of an optional value as the latter, the Optional type conforms to NilLiteralConvertible – presumably to return Optional.None, which indicates the lack of value under the hood.

All in all, literal conversions are one of those nifty conveniences in the Swift language. They can be great for making your custom data types more readable when used: simply typing 4 is much clearer than Complex(real: 4, imaginary: 0). On the other hand, pervasive use can actually cloud the evaluated type of a variable or expression – be careful not to obfuscate your code’s meaning with an implicit type conversion.

Want this post’s code as a playground? Of course you do! Download it here.