Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Neatly Typed Message

A Neatly Typed Message

The talk is about the readability of Swift code. I'll treat it not as a well-defined goal to achieve, but as a spectrum that you need to decide where to land on. Looking at the variations of popular Cocoa patterns and Swift language constructs we'll identify their readability tradeoffs and chances for improvement. We'll also learn some fine techniques to widen readability spectrum using the power of the Swift type system.

Krzysztof Siejkowski

March 03, 2017
Tweet

More Decks by Krzysztof Siejkowski

Other Decks in Technology

Transcript

  1. programs must be written for people to read, and only

    incidentally for machines to execute — Structure and Interpretation of Computer Programs book
  2. the essential material (are) techniques used to control the intellectual

    complexity of software systems. — Structure and Interpretation of Computer Programs book
  3. func split(email: String) -> (String, String)? { let components =

    email.components(separatedBy: "@") guard components.count == 2, let username = components.first, let domain = components.last, !username.isEmpty && !domain.isEmpty else { return nil } return (username, domain) } let splitEmail = split(email: ”[email protected]")
  4. func split(email: String) -> (String, String)? { let components =

    email.components(separatedBy: "@") guard components.count == 2, let username = components.first, let domain = components.last, !username.isEmpty && !domain.isEmpty else { return nil } return (username, domain) } let splitEmail = split(email: ”[email protected]")
  5. func split(email: String) -> (String, String)? { let components =

    email.components(separatedBy: "@") guard components.count == 2, let username = components.first, let domain = components.last, !username.isEmpty && !domain.isEmpty else { return nil } return (username, domain) } let splitEmail = split(email: ”[email protected]")
  6. Level 1-1: comments /// Splits the email into username and

    domain. /// - Parameter email: A string that possibly /// contains the email address value. /// - Returns: Either: /// - Tuple with username as first element /// and domain as second /// - Nil when passed argument cannot be split func split(email: String) -> (String, String)?
  7. Level 1-1: comments /// Splits the email into username and

    domain. /// - Parameter email: A string that possibly /// contains the email address value. /// - Returns: Either: /// - Tuple with username as first element /// and domain as second /// - Nil when passed argument cannot be split func split(email: String) -> (String, String)?
  8. Level 1-2: symbols typealias SplitEmail = (username: String, domain: String)

    struct SplitEmail { let username: String let domain: String } func split(email: String) -> SplitEmail?
  9. Level 1-3: wrappers enum SplitEmail { case valid(username: String, domain:

    String) case invalid init(from email: String) { let components = email.components(separatedBy: "@") if /* same checks as before */ { self = .valid(username: username, domain: domain) } else { self = .invalid }
  10. Level 1-4: concepts protocol Splitter { associatedtype Splitting associatedtype Splitted

    func split(value: Splitting) -> (Splitted, Splitted)? } struct EmailSplitter: Splitter { func split(value: String) -> (String, String)? { let components = value.components(separatedBy: "@") guard /* same checks as before */ else { return nil } return (first, second) }
  11. Level 1-4: concepts enum Split<Value, S: Splitter> where S.Splitting ==

    Value, S.Splitted == Value { case splitted(Value, Value) case invalid init(_ value: Value, using splitter: S) { if let (first, second) = splitter.split(value: value) { self = .splitted(first, second) } else { self = .invalid } See https://github.com/Ben-G/Validated/ by Benjamin Encz
  12. Level 1-4: concepts func split(email: String) -> Split<String, EmailSplitter> //

    result: Split // value: String // transform: EmailSplitter
  13. protocol Delegate: class { func foo() } struct Announcing {

    open weak var delegate: Delegate? } var announcing = Announcing() announcing.delegate = delegate 
  14. Level 2-1: Initializer injection struct Announcing { private weak var

    delegate: Delegate? init(to delegate: Delegate) { self.delegate = delegate } } let announcing = Announcing(to: delegate)
  15. Level 2-2: Weak closure struct Announcing { private let delegate:

    () -> Delegate? init(to delegate: @escaping () -> Delegate?) { … } } let announcing = Announcing(to: { [weak delegate] in delegate })
  16. Level 2-3: Weak function func weak<T: AnyObject>(_ object: T) ->

    () -> T? { return { [weak object] in object } } struct Announcing { private let delegate: () -> Delegate? init(to delegate: @escaping () -> Delegate?) { … } } let announcing = Announcing(to: weak(delegate))
  17. Level 2-4: Weak wrapper struct Weak<T: AnyObject> { private let

    internalValue: () -> T? init(_ value: T) { internalValue = { [weak value] in value } } } struct Announcing { private let delegate: Weak<Delegate> init(to delegate: Weak<Delegate>) { ... } }