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 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 Slide

  3. View Slide

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

    View Slide

  5. 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 Slide

  6. 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 Slide

  7. PATs are a bit weird
    why ?

    View Slide

  8. Comprendre c'est
    pardonner

    View Slide

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

    View Slide

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

    View 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

    View 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 delegate:Proto
    To: class C { var delegate:T } }

    View Slide

  13. 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 Slide

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

    View Slide

  15. View Slide

  16. How PATs are weird
    W3. The mysterious typealias

    View Slide

  17. 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 Slide

  18. 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 Slide

  19. 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 Slide

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

    View Slide

  21. No

    View Slide

  22. No
    And yes, sort of

    View Slide

  23. 2. Why PATs are weird

    View Slide

  24. 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 Slide

  25. 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 Slide

  26. 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 Slide

  27. 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 Slide

  28. 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 Slide

  29. 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 Slide

  30. 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 Slide

  31. 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 Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. View Slide

  38. 3. Is this the plan?
    Yes*

    View Slide

  39. 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 Slide

  40. View Slide

  41. View Slide

  42. * Existentials?

    View Slide

  43. View Slide

  44. Comprendre c'est
    pardonner

    View Slide

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

    View Slide

  46. 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 Slide

  47. 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 Slide

  48. 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 Slide

  49. 4. How to love them?

    View Slide

  50. Call them
    PATs

    View Slide

  51. 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 Slide

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

    View Slide