Swift Generics: A closer look

CocoaHeadsSKG

Dimitri James Tsiflitzis

Generics

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

 

For instance Swift’s Array and Dictionary types are both generic collections. So is ?. It's shorthand for Optional

Why?

/// Declare

func printIntElements(_ array: [Int]) 
{
    array.map { print($0) }
}

/// Call
printIntElements([1, 2, 3, 4, 5])

/// Console
1
2
3
4
5

Why?

func printDoubleElements(_ array: [Double]) 
{
    array.map { print($0) }
}

func printStringElements(_ array: [String]) 
{
    array.map { print($0) }
}

Generic functions

func printWhateverTypeElements<PlaceHolderTypeName>(_ array: [PlaceHolderTypeName]) 
{
    array.map { print($0) }
}

/// OR

func printWhateverTypeElements<T>(_ array: [T])
{
    array.map { print($0) }
}

Placeholder & actual type

All placeholder types do is define that the parameters that are declared as that type must be of the same type when called.

Type Parameters

  • Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as <T>).

 

  • Use it to define the type of a function’s parameters, or as the function’s return type, or as a type annotation within the body of the function.

 

  • You can provide more than one type parameter by writing multiple type parameter names within the angle brackets, separated by commas.​

 

Naming Type Parameters

From the docs:

 

Always give type parameters upper camel case names (such as T and MyTypeParameter) to indicate that they’re a placeholder for a type, not a value.

 

Element is a fan favourite too.

Type Parameters

func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] 
{
  return Array(dictionary)
}


let some = pairs(from: ["minimum": 199, "maximum": 299]) 
// result is [("maximum", 299), ("minimum", 199)]

let more = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"]) 
// result is [(2, "Generics"), (3, "Rule"), (1, "Swift")]

Constraining a Generic Type

func mid<T>(array: [T]) -> T? 
{
    guard 
        !array.isEmpty 
    else 
    { 
        return nil 
    }

    return array.sorted()[(array.count - 1) / 2]
}

/// 👹 on sorted
/// The problem is that for sorted() to work, the elements 
/// of the array need to be Comparable 

Constraining a Generic Type

func mid<T: Comparable>(array: [T]) -> T?
{
    guard 
        !array.isEmpty 
    else 
    { 
        return nil
    }
    
    return array.sorted()[(array.count - 1) / 2]
}

Constraining a Generic Type

extension Array where Element: Equatable
{
    mutating func removeObject(object: Element) 
    {
        guard 
            let index = self.index(of: object) 
        else
        { 
            return 
        }

        self.remove(at: index)
    }
}

Use case: A Stack

/// Int Stack

struct IntStack 
{

}

Interlude: Reference Vs. Value Types

Interlude: Reference Vs. Value Types

Swift from Objective-C is the heavy preference of value types over reference types.

 

value types keep a unique copy of their data, while reference types share a single copy of their data.

 

Swift represents a reference type as a class. There are many kinds of value types in Swift, such as struct, enum, and tuples

 

 

Interlude: Reference Vs. Value Types

let c = Car()

let d = c

Car Instance

class Car

Interlude: Reference Vs. Value Types

let c = Car()

let d = c

Car Instance

struct Car

Car Instance

Use case: A Stack

/// Int Stack

struct IntStack 
{
    public private(set) var items = [Int]()

    mutating func push(_ item: Int)
    {
        items.append(item)
    }

    mutating func pop() -> Int 
    {
        return items.removeLast()
    }
}

Use case: A Stack

/// Element Stack

struct Stack<Element>
{    
    public private(set) var items = [Element]()

    mutating func push(_ item: Element)
    {
        items.append(item)
    }

    mutating func pop() -> Element
    {
        return items.removeLast()
    }
}

Use case: A Stack

var stackOfStrings = Stack<String>()

stackOfStrings.push("ένα 1️⃣")
stackOfStrings.push("δύο 2️⃣")
stackOfStrings.push("τρία 3️⃣")
stackOfStrings.push("τέσσερα 4️⃣")

Use case: A Tree

Root

Leaf

Node

Node

Leaf

Leaf

Leaf

Level 0

Level 1

Level 2

Use case: A Tree

class Node
{
    var value: String
    var children: [Node] = []
    weak var parent: Node?

    init(value: String) 
    {
        self.value = value
    }

    func add(child: Node) 
    {
        children.append(child)
        child.parent = self
    }
}

Use case: A Tree

class Node<T> 
{

    var value: T
    weak var parent: Node?

    var children: [Node] = []
  
    init(value: T) 
    {
        self.value = value
    }
  
    func add(child: Node) 
    {
        children.append(child)
        child.parent = self
    }
}

Extending a Generic Type

extension Stack
{
    var topItem: Element? 
    {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

Extensions with a Generic Where Clause

extension Stack where Element: Equatable 
{
    func isTop(_ item: Element) -> Bool
    {
        guard 
            let topItem = items.last
        else 
        {
            return false
        }

        return topItem == item
    }
}

Associated Types

protocol Container 
{
    associatedtype Item

    mutating func append(_ item: Item)

    var count: Int { get }
}

Associated Types

struct Stack<Element>: Container
{
    var items = [Element]()

    mutating func push(_ item: Element) 
    {
        items.append(item)
    }
    
    mutating func pop() -> Element
    {
        return items.removeLast()
    }

    // conformance to the Container protocol
    mutating func append(_ item: Element) 
    {
        self.push(item)
    }
    
    var count: Int 
    {
        return items.count
    }
}

Generics is arguably one of the major benefits of using Swift over Objective-C.

 

By being able to associate generic types with things like collections, we can write code that is a lot more predictable and safe.

 

Anecdotal evidence. Swift app rarely crash. They might be full of bugs but they don't crash.

Ευχαριστούμε 

 

🎈

CocoaHeadsSKG

Dimitri James Tsiflitzis

Swift Generics

By tsif

Swift Generics

  • 153