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

Protocols with Associated Types, and How They Got That Way

Protocols with Associated Types, and How They Got That Way

This talk at the 2015 Function Swift Conference summarized challenges with Swift's Protocols With Associated Types, the reasons for their behavior, their roots in features from other languages, and whether to expect them to change.

video: https://www.youtube.com/watch?v=XWoNjiSPqI8&feature=youtu.be

Alexis Gallagher

December 12, 2015
Tweet

More Decks by Alexis Gallagher

Other Decks in Technology

Transcript

  1. Protocols with
    Associated Types
    and how they got that way
    (maybe)
    @alexisgallagher

    View full-size slide

  2. Protocols with Associated Pain
    1. Hurray, I'm using value types!
    2. Oh, I can't subclass.
    3. Hurray, I'm using protocols!
    4. Hurray, I'm adopting Equatable!
    5.

    View full-size slide

  3. Protocols with Associated Pain
    Plain protocols, work as expected:
    protocol MyProto {
    }
    struct Foo : MyProto {
    }
    let protoVar:MyProto = Foo() // <-- No problem!

    View full-size slide

  4. Protocols with Associated Pain
    Protocols with associated types, do not:
    protocol MyProto {
    typealias MyAssocType // declare requirement
    }
    struct Foo : MyProto {
    typealias MyAssocType = Int // meet requirement
    }
    let protoVar:MyProto = Foo() // <-- Nope!

    View full-size slide

  5. Protocols with Associated Pain
    Protocols requiring Self? Same story:
    protocol MyEquatable {
    typealias MySelf // Self is a special associated type ...
    }
    struct Foo : MyEquatable {
    typealias MySelf = Foo // ... always equal to adopting type
    }
    let protoVar:MyEquatable = Foo() // <-- Nope!

    View full-size slide

  6. PATs are a bit weird
    why ?

    View full-size slide

  7. Comprendre c'est
    pardonner

    View full-size slide

  8. Questions
    1. How are PATs weird?
    2. Why are PATs weird?
    3. Is this the plan?
    4. How to love them?

    View full-size slide

  9. How PATs are weird
    W1. Only usable as generic constraints

    View full-size slide

  10. Weirdness 1: only usable as generic constraints
    • This rule excludes PATs from literally every single use of
    "protocols" as defined in Objective-C

    View full-size slide

  11. Weirdness 1: only usable as generic constraints
    • This rule excludes PATs from literally every single use of
    "protocols" as defined in Objective-C
    • Everywhere you wanted a protocol, you need a generic:
    From: var delegate:Proto
    To: class C { var delegate:T } }

    View full-size slide

  12. Weirdness 1: only usable as generic constraints
    • This rule excludes PATs from literally every single use of
    "protocols" as defined in Objective-C
    • Everywhere you wanted a protocol, you need a generic:
    From: var delegates:[Proto]
    To: class C { var delegates:[T] } }
    • Which still excludes dynamic dispatch … which might be
    why you wanted a protocol to begin with!

    View full-size slide

  13. How PATs are weird
    W2. Docs describe them as "real" types

    View full-size slide

  14. How PATs are weird
    W3. The mysterious typealias

    View full-size slide

  15. Weirdness 3: the mysterious typealias
    typealias serves two different functions:
    1. Outside PATs: provides the syntactic convenience of
    meaningful names
    2. Inside PATs, establishes a semantic requirement on types
    adopting the PAT

    View full-size slide

  16. Weirdness 3: the mysterious typealias
    • typealias defines a placeholder for an unknown type ...
    • ... which can be used throughout the protocol definition
    protocol Animal {
    typealias Food
    func eat(food:Food) { }
    }
    This sounds familiar...

    View full-size slide

  17. Weirdness 3: the mysterious typealias
    typealias feels like a generic type parameter!
    struct Animal {
    func eat(food:Food) { }
    }
    protocol Animal {
    typealias Food
    func eat(food:Food) { }
    }

    View full-size slide

  18. Are PATs just generic protocols
    with whacky syntax??
    Does that explain everything?!

    View full-size slide

  19. No
    And yes, sort of

    View full-size slide

  20. 2. Why PATs are weird

    View full-size slide

  21. The problem associated types solve
    Subtyping alone cannot capture rich type relations
    protocol Food { }
    struct Grass : Food { }
    protocol Animal {
    func eat(f:Food)
    }
    struct Cow : Animal {
    func eat(f:Grass) { } // <- error, Cow must eat Food
    }

    View full-size slide

  22. The problem associated types solve
    Subtyping alone cannot capture rich type relations
    protocol Food { }
    struct Grass : Food { }
    protocol Animal {
    typealias Food
    func eat(f:Food)
    }
    struct Cow { }
    extension Cow : Animal {
    func eat(f:Food) { } // <- OK
    }

    View full-size slide

  23. But why not use generic protocol syntax?
    /// imaginary generic protocol swift
    protocol Animal {
    func eat(food:Food)
    }
    extension Cow : Animal {
    func eat(f:Grass) { }
    }

    View full-size slide

  24. But why not use generic protocol syntax?
    • Because from outside the protocol, consumers could only
    see the associated type by parameterizing over it:
    /// generic-protocol-swift
    func feedAnimal,F>(a:A) {
    // I see an F, and oh yeah F is a type variable for food
    }
    • But Swift PATs provide direct named access to associated
    type, like properties
    func feedAnimal(a:A) {
    // I see the assoc type: A.Food
    }

    View full-size slide

  25. But why not use generic protocol syntax?
    And this problem compounds when protocols:
    • have many associated types
    • which might themselves be constrained by protocols
    • and we want to use them all at once

    View full-size slide

  26. Generic Graph BFS in Java (v 1.3)
    public class breadth_first_search {
    public static <
    GraphT extends VertexListGraph &
    IncidenceGraph,
    Vertex,
    Edge extends GraphEdge,
    VertexIterator extends Collection,
    OutEdgeIterator extends Collection,
    ColorMap extends ReadWritePropertyMap,
    Visitor extends BFSVisitor>
    void go(GraphT g, Vertex s, ColorMap c, Visitor vis);
    }

    View full-size slide

  27. Generic Graph BFS in C++
    template
    void breadth_first_search(const G& g,
    typename graph traits::vertex s, C c, Vis vis);
    // constraints:
    // G models Vertex List Graph and Incidence Graph
    // C models Read/Write Map
    // map traits::key == graph traits::vertex
    // map traits::value models Color
    // Vis models BFS Visitor

    View full-size slide

  28. Generic Graph BFS in Haskell (Hugs 2002)
    breadth_first_search ::
    (VertexListGraph g v, IncidenceGraph g e v,
    ReadWriteMap c v Color, BFSVisitor vis a g e v) =>
    g ! v ! c ! vis ! a ! a

    View full-size slide

  29. 3. Is this the plan?
    Yes*

    View full-size slide

  30. Two Worlds of Protocols
    Without Self Requirement With Self Requirement
    func precedes(other: Ordered) -> Bool func precedes(other: Self) -> Bool
    Usable as a type
    func sort(inout a: [Ordered])
    Only usable as a generic constraint
    func sort(inout a: [T])
    Think “heterogeneous” Think “homogeneous”
    Every model must deal with all others Models are free from interaction
    Dynamic dispatch Static dispatch
    Less optimizable More optimizable

    View full-size slide

  31. * Existentials?

    View full-size slide

  32. Comprendre c'est
    pardonner

    View full-size slide

  33. Questions
    1. How are PATs weird?
    2. Why are PATs weird?
    3. Is this the plan?
    4. How to love them?

    View full-size slide

  34. Answers
    How PATs are weird
    • Lock you into generics and static dispatch
    • typealias syntax plays two roles
    • Associated type is as much like a required property as like a
    type parameter

    View full-size slide

  35. Answers
    Why PATs are weird
    • Rich, multi-type abstractions do not "fit" in OOP subtyping
    • Designed to address known issues with naive "generic
    protocol" designs (complexity scales badly with more
    associated types)

    View full-size slide

  36. Answers
    Yes, functioning as planned
    • Seems informed by C++ concepts, Haskell type classes, SML
    signatures, generics in C# and Java, abstract type members
    in Scala, etc.
    • Impact on OOP "protocol" pattern: collateral damage
    • Existentials?

    View full-size slide

  37. 4. How to love them?

    View full-size slide

  38. Call them
    PATs

    View full-size slide

  39. Answers
    How to love them
    • Call them PATs
    • Embrace generics
    • If you still need dynamic dispatch
    • use enums to push runtime variation into values
    • use type erasure to hide dynamic dispatch within a type
    • wait for existentials?

    View full-size slide

  40. Protocols with
    Associated Types
    and how they got that way
    (but now we can just ask on swift-evolution, right?)
    @alexisgallagher

    View full-size slide