Protocols with
Associated Types
and how they got that way
(maybe)
 
@alexisgallagher
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.
Protocols with Associated Pain
Plain protocols, work as expected:
protocol MyProto {
}
struct Foo : MyProto {
}
let protoVar:MyProto = Foo() // <-- No problem!
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!
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!
PATs are a bit weird
why ?
Comprendre c'est
pardonner
Questions
1. How are PATs weird?
2. Why are PATs weird?
3. Is this the plan?
4. How to love them?
How PATs are weird
W1. Only usable as generic constraints
Weirdness 1: only usable as generic constraints
• This rule excludes PATs from literally every single use of
"protocols" as defined in Objective-C
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 } }
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!
How PATs are weird
W2. Docs describe them as "real" types
How PATs are weird
W3. The mysterious typealias
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
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...
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) { }
}
Are PATs just generic protocols
with whacky syntax??
Does that explain everything?!
No
No
And yes, sort of
2. Why PATs are weird
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
}
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
}
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) { }
}
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
}
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
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);
}
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
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
3. Is this the plan?
Yes*
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
* Existentials?
Comprendre c'est
pardonner
Questions
1. How are PATs weird?
2. Why are PATs weird?
3. Is this the plan?
4. How to love them?
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
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)
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?
4. How to love them?
Call them
PATs
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?
Protocols with
Associated Types
and how they got that way
(but now we can just ask on swift-evolution, right?)
 
@alexisgallagher

Protocols with Associated Types, and How They Got That Way

  • 1.
    Protocols with Associated Types andhow they got that way (maybe)   @alexisgallagher
  • 2.
    Protocols with AssociatedPain 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.
  • 4.
    Protocols with AssociatedPain Plain protocols, work as expected: protocol MyProto { } struct Foo : MyProto { } let protoVar:MyProto = Foo() // <-- No problem!
  • 5.
    Protocols with AssociatedPain 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 AssociatedPain 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 abit weird why ?
  • 8.
  • 9.
    Questions 1. How arePATs weird? 2. Why are PATs weird? 3. Is this the plan? 4. How to love them?
  • 10.
    How PATs areweird W1. Only usable as generic constraints
  • 11.
    Weirdness 1: onlyusable as generic constraints • This rule excludes PATs from literally every single use of "protocols" as defined in Objective-C
  • 12.
    Weirdness 1: onlyusable 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: onlyusable 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 areweird W2. Docs describe them as "real" types
  • 16.
    How PATs areweird W3. The mysterious typealias
  • 17.
    Weirdness 3: themysterious 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: themysterious 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: themysterious 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 justgeneric protocols with whacky syntax?? Does that explain everything?!
  • 21.
  • 22.
  • 23.
    2. Why PATsare weird
  • 24.
    The problem associatedtypes 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 associatedtypes 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 notuse 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 notuse 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 notuse 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 BFSin 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 BFSin 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 BFSin 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
  • 38.
    3. Is thisthe plan? Yes*
  • 39.
    Two Worlds ofProtocols 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
  • 42.
  • 44.
  • 45.
    Questions 1. How arePATs weird? 2. Why are PATs weird? 3. Is this the plan? 4. How to love them?
  • 46.
    Answers How PATs areweird • 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 areweird • 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 asplanned • 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 tolove them?
  • 50.
  • 51.
    Answers How to lovethem • 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 andhow they got that way (but now we can just ask on swift-evolution, right?)   @alexisgallagher