Slide 1

Slide 1 text

Row, Row, Row your Go Design Patterns for Structural Polymorphism in Go Rebecca Skinner @cercerilla May 22, 2019 1

Slide 2

Slide 2 text

CC-BY-SA 4.0 2

Slide 3

Slide 3 text

1 TLDR 3

Slide 4

Slide 4 text

Ad Hoc Composable Interfaces You can define ad-hoc interfaces in Go, and use them for input or output parameters of functions. 4

Slide 5

Slide 5 text

Ad Hoc Composable Interfaces You can define ad-hoc interfaces in Go, and use them for input or output parameters of functions. type A interface { } type B interface { } func takesAB ( x interface {A; B} ) { } func returnsAB ( ) interface {A; B} { return struct { } { } } 4

Slide 6

Slide 6 text

Interfaces For Field Access You can also use interfaces to allow access to aribtrary nested fields in a structure. This works with embedded structures too. 5

Slide 7

Slide 7 text

Interfaces For Field Access You can also use interfaces to allow access to aribtrary nested fields in a structure. This works with embedded structures too. type T struct { A int B string F func ( int ) string } type HasA interface {A( ) int } type HasF interface {F ( ) func ( int ) string } 5

Slide 8

Slide 8 text

Ad-Hoc Interfaces with Accessor Interfaces You can combine these into something that resembles structural polymorphism, and maybe even extend the idea to row types. 6

Slide 9

Slide 9 text

Ad-Hoc Interfaces with Accessor Interfaces You can combine these into something that resembles structural polymorphism, and maybe even extend the idea to row types. % func callF ( x interface {HasA ; HasF } ) string { return x . F ( ) ( x .A ( ) ) } 6

Slide 10

Slide 10 text

Can We Run With This? Can We Extend This Idea Into Something Powerful? 7

Slide 11

Slide 11 text

Of Course We Can’t 8

Slide 12

Slide 12 text

frame Instead, Let’s Look At Why Go’s Type System Is Broken 9

Slide 13

Slide 13 text

2 The Polymorphism Problem 10

Slide 14

Slide 14 text

Making Simple Things Easy A good language should make easy simple things easy, and hard things possible. 11

Slide 15

Slide 15 text

But For Go Go feels like Simple Complex Easy × Hard × 12

Slide 16

Slide 16 text

2.1 An Identity Crisis 13

Slide 17

Slide 17 text

Simple. Easy. Impossible The identity function takes a value and returns it. 14

Slide 18

Slide 18 text

Why? The identity function can be very useful when working with high-order functions. 15

Slide 19

Slide 19 text

Silly Example % type idFunc = func ( int ) int func ReverseMapInt ( f idFunc , i n t s [ ] int ) [ ] int { out := [ ] int { } for _ , i := range i n t s { out = append ( [ ] int { f ( i ) } , out . . . ) } return } func IntID ( i int ) int { return i } func IntSucc ( i int ) int { return i + 1 } func main ( ) { i n t s := [ ] int {1 , 2 , 3} fmt . Println ( ReverseMapInt ( IntSucc , i n t s ) ) fmt . Println ( ReverseMapInt ( IntID , i n t s ) ) } 16

Slide 20

Slide 20 text

output % go run ∗. go [4 3 2] [3 2 1] 17

Slide 21

Slide 21 text

2.2 A Generic Problem 18

Slide 22

Slide 22 text

Problem Write an identity function that works for any input value. 19

Slide 23

Slide 23 text

% # ruby def id ( a ) return a ; end 20

Slide 24

Slide 24 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input 20

Slide 25

Slide 25 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a 20

Slide 26

Slide 26 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; 20

Slide 27

Slide 27 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; % / / rust fn id ( input : A) −> A { return input ; } 20

Slide 28

Slide 28 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; % / / rust fn id ( input : A) −> A { return input ; } % (∗ sml ∗) fun id ( x : ’ a ) : ’ a = x ; 20

Slide 29

Slide 29 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; % / / rust fn id ( input : A) −> A { return input ; } % (∗ sml ∗) fun id ( x : ’ a ) : ’ a = x ; % / / Javascript function id ( x ) { return x ; } 20

Slide 30

Slide 30 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; % / / rust fn id ( input : A) −> A { return input ; } % (∗ sml ∗) fun id ( x : ’ a ) : ’ a = x ; % / / Javascript function id ( x ) { return x ; } % / / Typescript function id (x : T ) : T { return x ; } 20

Slide 31

Slide 31 text

% # ruby def id ( a ) return a ; end % # python from typing import TypeVar , Generic A = TypeVar ( ’A ’ ) def id ( input : A) −> A: return input % − − haskell id a = a % (∗ ocaml ∗) l e t id a = a ; ; % / / rust fn id ( input : A) −> A { return input ; } % (∗ sml ∗) fun id ( x : ’ a ) : ’ a = x ; % / / Javascript function id ( x ) { return x ; } % / / Typescript function id (x : T ) : T { return x ; } % / / Java class I d e n t i t y { public static T id (T x ) { return x ; } } 20

Slide 32

Slide 32 text

And in Go 21

Slide 33

Slide 33 text

Polymorphism Go lacks the ability to directly express this type of polymorphism. Our only choices are to avoid this type of constructor, or else to fall back to using go generate or working with interface{}. 22

Slide 34

Slide 34 text

Reflection Reflection and interface{} give us an escape hatch of sorts from the type system. Most of the problems in this talk can be “solved” by lifting programs into the unityped space, but we will avoid this due to the difficulties in working with excessively reflective code. 23

Slide 35

Slide 35 text

3 The Many Shapes of Polymorphism 24

Slide 36

Slide 36 text

Types of Polymorphism This is just an example of one type of polymorphism, let’s look at a few others that are very common in modern languages: 25

Slide 37

Slide 37 text

3.1 Parametric Polymorphism 26

Slide 38

Slide 38 text

Parametric Polymorphism Parametric polymorphism is what we usually think of when we think of polymorphism. It let’s us write functions that can work for any type of value. For example: 27

Slide 39

Slide 39 text

Parametric Polymorphism Parametric polymorphism is what we usually think of when we think of polymorphism. It let’s us write functions that can work for any type of value. For example: • id 27

Slide 40

Slide 40 text

Parametric Polymorphism Parametric polymorphism is what we usually think of when we think of polymorphism. It let’s us write functions that can work for any type of value. For example: • id • Higher order functions like map and fold 27

Slide 41

Slide 41 text

Parametric Polymorphism Parametric polymorphism is what we usually think of when we think of polymorphism. It let’s us write functions that can work for any type of value. For example: • id • Higher order functions like map and fold • Higher Ranked Polymorphic Functions (e.g. polymorphic quantification over closures) 27

Slide 42

Slide 42 text

Parametric Polymorphism Parametric polymorphism is what we usually think of when we think of polymorphism. It let’s us write functions that can work for any type of value. For example: • id • Higher order functions like map and fold • Higher Ranked Polymorphic Functions (e.g. polymorphic quantification over closures) • Generic data structures 27

Slide 43

Slide 43 text

Generics as Parametric Polymorphism In properly parametric polymorphism, the type of the function is quantified over a set of types. Template generics don’t work that way since semantically they are more like metaprogramming to generate overloaded function, but in practice they fill a similar role in many languages. % func id ( type T ) ( val T) T { return val ; } 28

Slide 44

Slide 44 text

Return Type Polymorphism Return type polymorphism is a special case of parametric polymorphism where we determine which implementation of our function to use based on a return value type. % func parse ( type T ) ( unparsed string ) (T , error ) ; 29

Slide 45

Slide 45 text

3.2 Ad-Hoc Polymorphism 30

Slide 46

Slide 46 text

Ad-Hoc Polymorphism Ad-hoc polymorphism allows us to have different implementations of a function for different types. Function overloading is the typical example of ad-hoc polymorphism. 31

Slide 47

Slide 47 text

Interfaces As Polymorphism Interfaces are a sort of ad-hoc polymorphism that is useful and prevelant in idiomatic go code. 32

Slide 48

Slide 48 text

Postels Law Be conservative in what you send, and liberal in what you accept. 33

Slide 49

Slide 49 text

The Robustness Principle for APIs Accept interfaces and return concrete types. 34

Slide 50

Slide 50 text

Interface Based Ad Hoc Polymorphism % type AdHoc interface {AdHoc ( ) string } type A struct { } func ( a ∗A) AdHoc ( ) string { return "A. AdHoc" } type B struct { } func ( b ∗B) AdHoc ( ) string { return "B. AdHoc" } func polymorphic ( adhoc AdHoc) string { return adhoc . AdHoc ( ) } func main ( ) { fmt . Println ( polymorphic ( ( ∗A) ( n i l ) ) ) fmt . Println ( polymorphic ( ( ∗B) ( n i l ) ) ) } 35

Slide 51

Slide 51 text

Compare With Haskell % class AdHoc a where adHoc : : Proxy a −> String data A data B instance AdHoc A where adHoc = const "adHoc @A" instance AdHoc B where adHoc = const "adHoc @B" polymorphic : : AdHoc a => Proxy a −> String polymorphic = adHoc main : : IO ( ) main = do l e t a = Proxy @A b = Proxy @B putStrLn ( polymorphic a ) putStrLn ( polymorphic b ) 36

Slide 52

Slide 52 text

Limits of Go’s Interfaces The Limit’s of Go’s Interfaces 37

Slide 53

Slide 53 text

Return Type Polymorphism % class FromStr a where fromStr : : String −> a data A = A deriving Show instance FromStr A where fromStr = const A newtype B = B { runB : : Int } deriving (Show, Num) instance FromStr B where fromStr = const (B 0) main = do print $ fromStr @A " hello " print $ ( fromStr @B " world " ) + 1 38

Slide 54

Slide 54 text

In Go % type FromStr interface { fromStr ( string ) FromStr } type A struct { } func ( a A) fromStr ( s string ) FromStr { return A{ } } type B struct { x int } func ( b B) AddNum( y int ) B { return B{ b . x + y } } func ( b B) fromStr ( s string ) FromStr { return B{0} } func main ( ) { fmt . Println (A { } . fromStr ( " hello " ) ) fmt . Println (B{ 0 } . fromStr ( " world " ) . AddNum( 1 ) ) } 39

Slide 55

Slide 55 text

And in Go 40

Slide 56

Slide 56 text

Return Type Polymorphism % class Addable a where add : : a −> a −> a instance Addable Int where add = (+) data Point2D = Point2D { _x : : Int , _y : : Int } deriving Show instance Addable Point2D where add ( Point2D x1 y1 ) ( Point2D x2 y2 ) = Point2D ( x1 + x2 ) ( y1 + y2 ) type ShowAdd a = (Show a , Addable a ) polymorphic : : (ShowAdd a ) => a −> a −> String polymorphic a b = show ( add a b ) main = do l e t a = 1 : : Int ; b = 2 : : Int putStrLn $ polymorphic a b l e t a = Point2D 1 2; b = Point2D 3 5 putStrLn $ polymorphic a b 41

Slide 57

Slide 57 text

3.3 Subtype Polymorphism 42

Slide 58

Slide 58 text

Subtype Polymorphism In subtype polymorphism we have a partial ordering over interfaces. A1≤:B1 A2≤:B2 B1→A2≤:A1→B2 43

Slide 59

Slide 59 text

Interface Embedding as Subtype Polymorphism % type B1 interface { } type A1 interface {B1} type B2 interface { } type A2 interface {B2} type F1 interface { f1 (B1) A2} type F2 interface { f2 (A1) B2} func f2_from_f1 ( b1 A1) B2 { } 44

Slide 60

Slide 60 text

4 An Introduction to Row Types and Go 45

Slide 61

Slide 61 text

What Are Row Types? What are row types? 46

Slide 62

Slide 62 text

What Are Row Types? Row types provide a polymorphic abstraction over records. They allow us to express operations over a set of fields. 47

Slide 63

Slide 63 text

What Are Row Types? Row types provide a polymorphic abstraction over records. They allow us to express operations over a set of fields. τ : {ℓ0 : τ0, ℓ1 : τ1, ρ} 47

Slide 64

Slide 64 text

So What Are Row Types? So What Are Row Types? 48

Slide 65

Slide 65 text

So What Are Row Types? Row polymorphism is a way to make functions that are polymorphic over the fields in a record. On the surface, it’s similar to structural subtyping, since it allows us to abstract over the structure of a record. 49

Slide 66

Slide 66 text

Structural Typing Structural Typing? Go Can Do That 50

Slide 67

Slide 67 text

Structural Typing Structural typing uses the “shape” of a value to infer it’s type. We’ve seen a lot of examples of this pattern already. 51

Slide 68

Slide 68 text

Structural Typing Structural typing uses the “shape” of a value to infer it’s type. We’ve seen a lot of examples of this pattern already. interfaces! 51

Slide 69

Slide 69 text

Recall Remember 52

Slide 70

Slide 70 text

Ad Hoc Composable Interfaces You can define ad-hoc interfaces in Go, and use them for input or output parameters of functions. 53

Slide 71

Slide 71 text

Ad Hoc Composable Interfaces You can define ad-hoc interfaces in Go, and use them for input or output parameters of functions. % type A interface { } type B interface { } func takesAB ( x interface {A; B} ) { } func returnsAB ( ) interface {A; B} { return struct { } { } } 53

Slide 72

Slide 72 text

Interfaces For Field Access You can also use interfaces to allow access to aribtrary nested fields in a structure. This works with embedded structures too. 54

Slide 73

Slide 73 text

Interfaces For Field Access You can also use interfaces to allow access to aribtrary nested fields in a structure. This works with embedded structures too. type T struct { A int B string F func ( int ) string } type HasA interface {A( ) int } type HasF interface {F ( ) func ( int ) string } 54

Slide 74

Slide 74 text

Ad-Hoc Interfaces with Accessor Interfaces You can combine these into something that resembles structural polymorphism, and maybe even extend the idea to row types. 55

Slide 75

Slide 75 text

Ad-Hoc Interfaces with Accessor Interfaces You can combine these into something that resembles structural polymorphism, and maybe even extend the idea to row types. func callF ( x interface {HasA ; HasF } ) string { return x . F ( ) ( x .A ( ) ) } 55

Slide 76

Slide 76 text

4.1 From Structural Types to Row Types 56

Slide 77

Slide 77 text

The Difference between Structural and Row Types Structural types seem similar to row types on the surface, but there are some differences. 57

Slide 78

Slide 78 text

Quantification Row types should support quanitification over fields. It’s not sufficent to have: {ℓ0 : int, ℓ1 : string} Instead we need to be able to express the notion of interfaces over fields. 58

Slide 79

Slide 79 text

Field Level Constraints func rowFunc ( x interface { GetF ( ) interface { AsInt ( ) int } } ) int { return x . GetF ( ) . AsInt ( ) } func main ( ) { fmt . Println ( rowFunc ( Foo { } ) ) } 59

Slide 80

Slide 80 text

5 The Fundamental Problem 60

Slide 81

Slide 81 text

Type Erasure Interfaces ̸= Constraints Even when we can construct appropriate constraints based on our ad-hoc and structural polymorphism, the ergonomics are poor due to the differences between interfaces and constraints. 61

Slide 82

Slide 82 text

Interfaces are Surjective Lifting values to interfaces is an epimorphism. Without resorting to runtime type reflection we don’t know what type we put into an interface: type A struct { } type B struct { } type C interface { } func ifaceFunc ( c C) C { return c } func main ( ) { fmt . Println ( ifaceFunc (A { } ) ) fmt . Println ( ifaceFunc (B { } ) ) } 62

Slide 83

Slide 83 text

Non-Constructive Interfaces Interfaces can’t construct values of the type that implement them. This limits us to interfaces that work on already constructed values. 63

Slide 84

Slide 84 text

6 Summary 64

Slide 85

Slide 85 text

Summary • There are a lot of different types of polymorphism • Go’s interfaces give us a lot of them • Go’s type system is a lot more flexible than you might thing • But doing interesting stuff has poor ergonomics • Generics would resolve a lot of these problems • But even syntactic sugar over a few of these idioms would make go more usable 65

Slide 86

Slide 86 text

7 Questions? 66