Protocol-Oriented Programming in Swift

«  Try Vapor
ML Reading Ch04 - Training Models  »

At the heart of Swift’s design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of these concepts benefit predictability, performance, and productivity, but together they can change the way we think about programming.

Classes

  • Encapulation
  • Access control
  • Abstraction
  • Namespace
  • Expressive syntax
  • Extensibility

=> struct and enum can also succeed access control, abstraction, namespace part

  • Inherited
    • customization points and reuse

1. Implicit Sharing

Cocoa Collection Sharing:

NOTE: It is not safe to modify a mutable collection while enumerating through it. Some enumerators may currently allow enumeration of a collection that is modified, but this behavior is not guaranteed to be supported in the future.

Not occurs on Swift, because Swift is value types, don’t share. The thing we are iterating and modifying is distinct.

2. Inheritance All up in Your business

  • Single inheritance weight gain
  • No retroactive modeling
  • Superclass may have stored properties
    • initialization required
    • clear how to interact with superclass without break superclass invariants
    • know when to override

=> promote delegation

3. Lost Type Relationships

class Ordered {
    func precedes(other: Ordered) -> Bool {
        // NOT GOOD
        fatalError("implement me!")
    }
}

class Label: Ordered { var text: String = "" ... }

class Number: Ordered {
    var value: Double = 0
    override func precedes(other: Ordered) -> Bool {
        // we have to downcast it to use value
        return value < (other as! Number).value
    }
}

If we found this as! ASubclass, this is a sign that a type relationship was lost. Usually due to using classes for abstraction.

A better abstraction mechanism:

  • supports value types ( and classes )
  • support static type relationships (and dynamic dispatch)
  • non-monolithic
  • supports retroactive modeling
  • doesn’t impose instance data on models
  • doesn’t impose initialization burdens on models
  • makes clear what to implement

=> made Protocol

Swift is a Protocol-Oriented Programming Language.

Start with Protocol

protocol Ordered {
    // use Self to identify used Type
    func preceds(other: Self) -> Bool
}

struct Number: Ordered {
    var value: Double = 0
    func precedes(other: Number) -> Bool {
        return self.value < other.value
    }
}

// use generic type
func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {
    ...
}

It’s quite different with Self requirements.

Protocol Extension

Can be a requirements create customization points.

Constrained extensions: -> 2 generator types might not be able to check ==, we’d define a where to give it needs to allow comparison:

// use `where Generator.Element: Equatable`
extension CollectionType where Generator.Element: Equatable {
    public func indexOf(element: Generator.Element) -> Index? {
        for i in self.indices {
            // can check == now
            if self[i] == element {
                return i
            }
        }
        return nil
    }
}

Retroactive adaptation:

protocol Ordered {
    func precedes(other: Self) -> Bool
}
func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {...}

extension Int: Ordered {
    func precedes(other: Int) -> Bool { return self < other }
}

extension String: Ordered {
    func precedes(other: String) -> Bool { return self < other }
}

TO =>

extension Ordered where Self: Comparable {
    func precedes(other: String) -> Bool { return self < other }
}

Bridge-Building

Build bridge between static and dynamic:

protocol Drawable {
    func isEqualTo(other: Drawable) -> Bool
    func draw()
}

extension Drawable where Self: Equatable {
    func isEqualTo(other: Drawable) -> Bool {
        if let o = other as? Self { return self == o }
        return false
    }
}

When to use classes

  • want implicit sharing when
    • copying or comparing instances doesn’t make sense(e.g., Window)
    • instance lifetime is tied to external effects(e.g., TemporaryFile)
    • instances are just “sinks” - write-only conduits to external state (e.g., CGContext)
  • Don’t fight the system
    • if a framework expects you to subclass, or to pass an object, do it
    • when factoring something out of a class, consider a non-class

Reference

Published on 28 Jun 2021 Find me on Facebook, Twitter!

«  Try Vapor
ML Reading Ch04 - Training Models  »

Comments

    Join the discussion for this article at here . Our comments is using Github Issues. All of posted comments will display at this page instantly.