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

4b71cdf53bc76edd1a2ab66c446954b5?s=128

Alexis Gallagher

December 12, 2015
Tweet

Transcript

  1. Protocols with Associated Types and how they got that way

    (maybe) @alexisgallagher
  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.
  3. None
  4. Protocols with Associated Pain Plain protocols, work as expected: protocol

    MyProto { } struct Foo : MyProto { } let protoVar:MyProto = Foo() // <-- No problem!
  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!
  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!
  7. PATs are a bit weird why ?

  8. Comprendre c'est pardonner

  9. Questions 1. How are PATs weird? 2. Why are PATs

    weird? 3. Is this the plan? 4. How to love them?
  10. How PATs are weird W1. Only usable as generic constraints

  11. Weirdness 1: only usable as generic constraints • This rule

    excludes PATs from literally every single use of "protocols" as defined in Objective-C
  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<T:Proto> { var delegate:T } }
  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<T:Proto> { var delegates:[T] } } • Which still excludes dynamic dispatch … which might be why you wanted a protocol to begin with!
  14. How PATs are weird W2. Docs describe them as "real"

    types
  15. None
  16. How PATs are weird W3. The mysterious typealias

  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
  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...
  19. 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) { } }
  20. Are PATs just generic protocols with whacky syntax?? Does that

    explain everything?!
  21. No

  22. No And yes, sort of

  23. 2. Why PATs are weird

  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 }
  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 }
  26. 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) { } }
  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<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 }
  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
  29. 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); }
  30. 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
  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
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. 3. Is this the plan? Yes*

  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<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
  40. None
  41. None
  42. * Existentials?

  43. None
  44. Comprendre c'est pardonner

  45. Questions 1. How are PATs weird? 2. Why are PATs

    weird? 3. Is this the plan? 4. How to love them?
  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
  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)
  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?
  49. 4. How to love them?

  50. Call them PATs

  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?
  52. Protocols with Associated Types and how they got that way

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