Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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!

Slide 6

Slide 6 text

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!

Slide 7

Slide 7 text

PATs are a bit weird why ?

Slide 8

Slide 8 text

Comprendre c'est pardonner

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

How PATs are weird W1. Only usable as generic constraints

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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!

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

How PATs are weird W3. The mysterious typealias

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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) { } }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

No

Slide 22

Slide 22 text

No And yes, sort of

Slide 23

Slide 23 text

2. Why PATs are weird

Slide 24

Slide 24 text

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 }

Slide 25

Slide 25 text

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 }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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 }

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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); }

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

3. Is this the plan? Yes*

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

* Existentials?

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Comprendre c'est pardonner

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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)

Slide 48

Slide 48 text

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?

Slide 49

Slide 49 text

4. How to love them?

Slide 50

Slide 50 text

Call them PATs

Slide 51

Slide 51 text

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?

Slide 52

Slide 52 text

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