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 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.
  2. Protocols with Associated Pain Plain protocols, work as expected: protocol

    MyProto { } struct Foo : MyProto { } let protoVar:MyProto = Foo() // <-- No problem!
  3. 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!
  4. 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!
  5. Questions 1. How are PATs weird? 2. Why are PATs

    weird? 3. Is this the plan? 4. How to love them?
  6. Weirdness 1: only usable as generic constraints • This rule

    excludes PATs from literally every single use of "protocols" as defined in Objective-C
  7. 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<T:Proto> { var delegate:T } }
  8. 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<T:Proto> { var delegates:[T] } } • Which still excludes dynamic dispatch … which might be why you wanted a protocol to begin with!
  9. 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
  10. 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...
  11. Weirdness 3: the mysterious typealias typealias feels like a generic

    type parameter! struct Animal<Food> { func eat(food:Food) { } } protocol Animal { typealias Food func eat(food:Food) { } }
  12. No

  13. 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 }
  14. 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 }
  15. But why not use generic protocol syntax? /// imaginary generic

    protocol swift protocol Animal<Food> { func eat(food:Food) } extension Cow : Animal<Grass> { func eat(f:Grass) { } }
  16. 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<A:Animal<F>,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:Animal>(a:A) { // I see the assoc type: A.Food }
  17. 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
  18. Generic Graph BFS in Java (v 1.3) public class breadth_first_search

    { public static < GraphT extends VertexListGraph<Vertex, VertexIterator, ?> & IncidenceGraph<Vertex, Edge, OutEdgeIterator, ?>, Vertex, Edge extends GraphEdge<Vertex>, VertexIterator extends Collection<Vertex>, OutEdgeIterator extends Collection<Edge>, ColorMap extends ReadWritePropertyMap<Vertex, ColorValue>, Visitor extends BFSVisitor<GraphT, Vertex, Edge>> void go(GraphT g, Vertex s, ColorMap c, Visitor vis); }
  19. Generic Graph BFS in C++ template <class G, class C,

    class Vis> void breadth_first_search(const G& g, typename graph traits<G>::vertex s, C c, Vis vis); // constraints: // G models Vertex List Graph and Incidence Graph // C models Read/Write Map // map traits<C>::key == graph traits<G>::vertex // map traits<C>::value models Color // Vis models BFS Visitor
  20. 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
  21. 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<T : Ordered>(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
  22. Questions 1. How are PATs weird? 2. Why are PATs

    weird? 3. Is this the plan? 4. How to love them?
  23. 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
  24. 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)
  25. 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?
  26. 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?
  27. Protocols with Associated Types and how they got that way

    (but now we can just ask on swift-evolution, right?) @alexisgallagher