Literal can be used in Swift. Did you make it?

Keywords: Swift


Brief comment: Look at this Swift code:
let link:LinkedList = [1,2,3,4,5,6,7,8]
link is not an array. After reading this article, you will understand.

Since 2012, NSDictionary, NSArray, NSNumber or NSString can be initialized using "literal" expressions.

NSNumber *numb = @1;
NSArray *things = @[@3,@45,@65];
NSDictionary *map = @{@"Florida" : @1 , @"Nevada" : @6 };
NSString *greet = @"Hello there!";

This "literal" expression makes the code more concise.

Have you ever thought about using literal expressions to initialize your data structures?

Swift provides such protocols as ExpressibleBy_:

  • Expressible By Array Literal
  • Expressible By Boolean Literal
  • Expressible By Dictionary Literal
  • Expressible By Float Literal
  • Expressible By Integer Literal
  • Expressible By String Literal
  • Expressible By Unicode Scalar Literal
  • Expressible By Extended Grapheme Cluster Literal

You can use literal expressions to initialize your own data structures. You can see the following examples of how to use literal expressions to construct your own data structures.

Array-ish data structure

Let's look at the example in Swift, [1, 2, 3, 4, 5, 6, 7, 8] which is probably used to initialize an Array. But! It can also be used to initialize other data structures.

linked list

The following code describes how to initialize a linked list using Array literal:

class LinkedList<T> : ExpressibleByArrayLiteral, CustomDebugStringConvertible {
    var debugDescription: String {
        get {
            var accum = ""
            guard let startNode = self.head else { return "(empty list)" }
            accum += startNode.debugDescription
            var currentNode:Node = startNode
            while(true) {
                guard let node = currentNode.next else { break }
                accum += node.debugDescription
                currentNode = node
            }
            return accum
        }
    }
    var head:Node?
    var tail:Node?
    typealias Element = T
    required public init(arrayLiteral elements:Element...) {
        for i in elements {
            if self.head == nil {
                self.head = Node(data:i)
                self.tail = self.head
                continue
            }
            let newNode = Node(data:i)
            self.tail?.next = newNode
            self.tail = newNode
        }
    }
    class Node : CustomDebugStringConvertible {
        var debugDescription: String {
            get {
                var accum = "(\(data))->"
                if next == nil {
                    accum += "*"
                }
                return accum
            }
        }
        var next:Node?
        var data:T
        init(data:T) {
            self.data = data
        }
    }
}
let link:LinkedList<Int> = [1,2,3,4,5,6,7,8]
print(link)   // Prints (1)->(2)->(3)->(4)->(5)->(6)->(7)->(8)->*

The LinkedList class follows the ExpressibleByArray Literal protocol and implements the init(arrayLiteral: Self.Element...) initialization method, so we can initialize it directly using Array literal assignment.

let link:LinkedList<Int> = [1,2,3,4,5,6,7,8]

Note: Array literal must be used to assign values, not an array, because these are two different things, such as:

let someArray = [1,2,3,4]
let list:LinkedList<Int> = someArray  // This code will report an error.

stack

struct ConvertibleStack<T> : ExpressibleByArrayLiteral, CustomDebugStringConvertible {

    var debugDescription: String {
        get {
            return "Stack: \(storage)"
        }
    }
    typealias Element = T
    private var storage: [T] = []

    public init(arrayLiteral elements:Element...) {
        self.storage = elements
    }

    mutating func push(item:T) {
        storage.insert(item, at: 0)
    }

    mutating func pop() -> T? {
        guard storage.count > 0 else { return nil }
        return storage.remove(at: 0)
    }
}

queue

struct ConvertibleStack<T> : ExpressibleByArrayLiteral, CustomDebugStringConvertible {

    var debugDescription: String {
        get {
            return "Stack: \(storage)"
        }
    }
    typealias Element = T
    private var storage: [T] = []

    public init(arrayLiteral elements:Element...) {
        self.storage = elements
    }

    mutating func push(item:T) {
        storage.insert(item, at: 0)
    }

    mutating func pop() -> T? {
        guard storage.count > 0 else { return nil }
        return storage.remove(at: 0)
    }
}

Finally, if litera initialization is used correctly, readability can be improved. Is this method suitable for trees or directed graphs? I leave this exploration to the reader:) Happy Hacking!

Original: Express Yourself Swift Style

Recommended reading:

Posted by farkewie on Sun, 23 Jun 2019 16:29:34 -0700