/ SWIFT

Using Initialization with Literals to Design Richer Swift API

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. … [Therefore,] making it easy to read makes it easier to write.” - Robert C. Martin

Rephrasing Robert Martin, one should not neglect readability of their code in favor of speed of writing it. In this post we’ll see how Initialization with Literals can help us to build more expressive and richer APIs.

Explaining Initialization with Literals

Literal is a notation for representing a fixed value in source code. Such notations as integers, floating-point numbers, strings, booleans and characters are literals. Literals are not limited to atomic values. The compound objects like array and dictionary fall within the scope of this definition as well.

let a = 1

Here, 1 is an integer literal that is used to initialize a constant a.

Literals are the essential blocks of the code and implementing shorthands for them makes your code more clean and direct. Swift has a family of ExprissibleByLiteral protocols that allows structs, classes and enums to be initialized using a literal. Examine how conformance to ExpressibleByIntegerLiteral protocol of integer and floating-point types from Swift Standard Library allows both of them to be initialized with an integer literal:

// Type inferred as 'Int'
let intValue = 1

// A floating-point value initialized using an integer literal. Type inferred as 'Double'
let doubleValue: Double = 1

The full list of protocols is next:

  • ExpressibleByArrayLiteral
  • ExpressibleByDictionaryLiteral
  • ExpressibleByIntegerLiteral
  • ExpressibleByFloatLiteral
  • ExpressibleByBooleanLiteral
  • ExpressibleByNilLiteral
  • ExpressibleByStringLiteral
  • ExpressibleByExtendedGraphemeClusterLiteral
  • ExpressibleByUnicodeScalarLiteral

Let’s discuss them one by one together with practical examples.

ExpressibleByStringLiteral

ExpressibleByStringLiteral stands for a type that can be initialized with a string literal. The initializer init(stringLiteral: Self.StringLiteralType) is the only method that needs to be implemented for this protocol conformance.

ExpressibleByExtendedGraphemeClusterLiteral stands for the type that can be initialized with a string containing a single extended grapheme cluster, e.g. “ந”. More about the extended grapheme clusters can be found in Unicode standard.

ExpressibleByUnicodeScalarLiteral can be initialized with a string containing a single Unicode scalar value. e.g. “♥”.

When conforming to ExpressibleByStringLiteral, you should always evaluate whether your type can be initialized from a Unicode scalar and a Grapheme Cluster literal, and implement them as well.

ExpressibleByStringLiteral and URL

When defining manually typed URLs and URL requests, it is handful to initialize them with a strings omitting URL’s initializer.

extension URL: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        self = URL(string: value)!
    }
}

Now both URL and URL request can be initialized with a string:

let url: URL = "https://www.vadimbulavin.com"
print(url) // prints 'https://www.vadimbulavin.com'

let request = URLRequest(url: "https://www.vadimbulavin.com")
print(request) // prints 'https://www.vadimbulavin.com'

ExpressibleByStringLiteral and NSRegularExpression

Swift borrows NSRegularExpression class from Objective-C. We can write our own wrapper on top of it adding some syntactic sugar:

struct RegularExpression {
    private let regex: NSRegularExpression

    init(regex: NSRegularExpression) {
        self.regex = regex
    }

    func matches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
        return regex.matches(in: string, options: options, range: NSMakeRange(0, string.count))
    }
}

extension RegularExpression: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        let regex = try! NSRegularExpression(pattern: value, options: [])
        self.init(regex: regex)
    }
}

let regex: RegularExpression = "abc"
print(regex.matches(in: "abc")) // prints that match is found

ExpressibleByIntegerLiteral

ExpressibleByIntegerLiteral represents a type that can be initialized with an integer literal.

ExpressibleByIntegerLiteral and UILayoutPriority

The pre-defined auto layout priorities are often not enough to express complex constraints. Lets define convenience methods to set custom UILayoutPriority.

extension UILayoutPriority: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) {
        self.init(rawValue: Float(value))
    }
}

extension NSLayoutConstraint {
    func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
        self.priority = priority
        return self
    }
}

Now you can set custom priorities with an integer:

let label = UILabel()
label.setContentHuggingPriority(1, for: .horizontal)

let anotherLabel = UILabel()
label.leadingAnchor.constraint(equalTo: anotherLabel.leadingAnchor).withPriority(1).isActive = true

ExpressibleByIntegerLiteral and Date

Convenience initializer for a Date. Especially useful for hardcoded dates in Unit tests.

extension Date: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMddyyyy"
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        self = formatter.date(from: String(value)) ?? Date()
    }
}

let date: Date = 01_01_2000
print(date) // prints '2000-01-01 00:00:00 +0000'

ExpressibleByNilLiteral

Apple discourages from conforming to ExpressibleByNilLiteral for custom types. Presently only the Optional type conforms to it, which is used to express the absence of a value. The following example demonstrates incorrect usage of ExpressibleByNilLiteral that confuses the notion of absence of value with zero value.

struct MyStruct {
    let value: Int
}

extension MyStruct: ExpressibleByNilLiteral {
    init(nilLiteral: ()) {
        self = MyStruct(value: 0)
    }
}

let myStruct: MyStruct = nil
print(myStruct) // prints 'MyStruct(value: 0)'

Always use the Optional type to express the notion of value absence.

ExpressibleByArrayLiteral

Conform to ExpressibleByArrayLiteral protocol when implementing custom collections that are backed by an Array, ex. Stack, Graph, Queue, LinkedList etc. Here is a simple example of a Sentence struct that demonstrates the idea.

struct Sentence {
    let words: [String]
}

extension Sentence: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: String...) {
        self = Sentence(words: elements)
    }
}

An array type and an array literal are different things. This means that you can’t initialize a type that conforms to ExpressibleByArrayLiteral by assigning an existing array to it.

According to the above, the following example does not compile:

let sentence: Sentence = ["A", "B", "C"]
print(sentence) // prints 'Sentence(words: ["A", "B", "C"])'

let words = ["A", "B", "C"]
let anotherSentence: Sentence = words // error: Cannot convert value of type '[String]' to specified type 'Sentence'

ExpressibleByDictionaryLiteral

Similar to its Array counterpart, ExpressibleByDictionaryLiteral is primarily used for custom collections and types that are backed by a dictionary. HTTP request headers are among such examples.

struct HTTPHeaders {
    private let headers: [String: String]
}

extension HTTPHeaders: ExpressibleByDictionaryLiteral {
    init(dictionaryLiteral elements: (String, String)...) {
        var headers: [String: String] = [:]
        for pair in elements {
            headers[pair.0] = pair.1
        }
        self = HTTPHeaders(headers: headers)
    }
}

A dictionary literal should not be confused with an instance of Dictionary. Thus it is not possible to initialize a type that conforms to ExpressibleByDictionaryLiteral by assigning a Dictionary instance to it.

And here is an example that demonstrates the above nuance:

let headers: HTTPHeaders = ["Content-Type": "application/json"]
print(headers) // prints 'HTTPHeaders(headers: ["Content-Type": "application/json"])'

let headersDictionary = ["Content-Type": "application/json"] // headersDictionary is an instance of Dictionary, not a Dictionary literal
let anotherHeaders: HTTPHeaders = headersDictionary // error: Cannot convert value of type '[String : String]' to specified type 'HTTPHeaders'

Wrapping Up

Literal is a notation for representing a fixed value in source code. Integers, strings, booleans, floating-points, arrays, dictionaries are all literals.

An instance of Array and an array literal are two different things and they should not be confused. The same is true for Dictionary.

Swift contains a family of ExpressibleByLiteral protocols that are used for custom types to be initialized with a matching literal.

A number of practical examples were examined to demonstrate that Initialization with Literals can make your Swift code more clean and direct.


Thanks for reading!

If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.

Vadim Bulavin

Creator of Yet Another Swift Blog. Coding for fun since 2008, for food since 2012.

Follow