Slide 1

Slide 1 text

Will contracts replace interfaces? @francesc

Slide 2

Slide 2 text

● Interfaces ● Generics draft: ○ Type Parameters ○ Contracts ● Interfaces vs Contracts Agenda

Slide 3

Slide 3 text

what is an interface?

Slide 4

Slide 4 text

"In object-oriented programming, a protocol or interface is a common means for unrelated objects to communicate with each other" - wikipedia

Slide 5

Slide 5 text

"In object-oriented programming, a protocol or interface is a common means for unrelated objects to communicate with each other" - wikipedia

Slide 6

Slide 6 text

"In object-oriented programming, a protocol or interface is a common means for unrelated objects to communicate with each other" - wikipedia

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

what is a Go interface?

Slide 11

Slide 11 text

abstract types concrete types

Slide 12

Slide 12 text

concrete types in Go - they describe a memory layout - behavior attached to data through methods int32 int64 int16 int8 type Number int func (n Number) Positive() bool { return n > 0 }

Slide 13

Slide 13 text

int *os.File *strings.Reader *gzip.Writer []bool

Slide 14

Slide 14 text

type Positiver interface { Positive() bool } abstract types in Go - they describe behavior - they define a set of methods, without specifying the receiver io.Reader io.Writer fmt.Stringer

Slide 15

Slide 15 text

type Reader interface { Read(b []byte) (int, error) } type Writer interface { Write(b []byte) (int, error) } two interfaces

Slide 16

Slide 16 text

int *os.File *strings.Reader *gzip.Writer []bool io.Reader io.Writer

Slide 17

Slide 17 text

type ReadWriter interface { Read(b []byte) (int, error) Write(b []byte) (int, error) } union of interfaces

Slide 18

Slide 18 text

type ReadWriter interface { Reader Writer } union of interfaces

Slide 19

Slide 19 text

int *os.File *strings.Reader *gzip.Writer []bool io.Reader io.Writer io.ReadWriter

Slide 20

Slide 20 text

int *os.File *strings.Reader *gzip.Writer []bool io.Reader io.Writer io.ReadWriter ?

Slide 21

Slide 21 text

interface{}

Slide 22

Slide 22 text

“interface{} says nothing” - Rob Pike in his Go Proverbs

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

why do we use interfaces?

Slide 25

Slide 25 text

- writing generic algorithms - hiding implementation details - providing interception points why do we use interfaces?

Slide 26

Slide 26 text

a) func WriteTo(f *os.File) error b) func WriteTo(w io.ReadWriteCloser) error c) func WriteTo(w io.Writer) error d) func WriteTo(w interface{}) error what function do you prefer?

Slide 27

Slide 27 text

Cons: ● how would you test it? ● what if you want to write to memory? Pros: ● ? a) func WriteTo(f *os.File) error

Slide 28

Slide 28 text

Cons: ● how do you even write to interface{}? ● probably requires runtime checks Pros: ● you can write really bad code d) func WriteTo(w interface{}) error

Slide 29

Slide 29 text

Which ones does WriteTo really need? - Write - Read - Close b) func WriteTo(w io.ReadWriteCloser) error c) func WriteTo(w io.Writer) error

Slide 30

Slide 30 text

“The bigger the interface, the weaker the abstraction” - Rob Pike in his Go Proverbs

Slide 31

Slide 31 text

“Be conservative in what you do, be liberal in what you accept from others” - Robustness Principle

Slide 32

Slide 32 text

“Be conservative in what you send, be liberal in what you accept” - Robustness Principle

Slide 33

Slide 33 text

Abstract Data Types

Slide 34

Slide 34 text

Abstract Data Types Mathematical model for data types Defined by its behavior in terms of: - possible values, - possible operations on data of this type, - and the behavior of these operations

Slide 35

Slide 35 text

top(push( , ))= X S X

Slide 36

Slide 36 text

pop(push( , ))= S X S

Slide 37

Slide 37 text

empty(new()) not empty(push(S, X))

Slide 38

Slide 38 text

Axioms: top(push(S, X)) = X pop(push(S, X)) = S empty(new()) !empty(push(S, X)) Example: stack ADT

Slide 39

Slide 39 text

type Stack interface { Push(v interface{}) Stack Top() interface{} Pop() Stack Empty() bool } a Stack interface

Slide 40

Slide 40 text

func Size(s Stack) int { if s.Empty() { return 0 } return Size(s.Pop()) + 1 } algorithms on Stack

Slide 41

Slide 41 text

type Interface interface { Less(i, j int) bool Swap(i, j int) Len() int } a sortable interface

Slide 42

Slide 42 text

func Sort(s Interface) func Stable(s Interface) func IsSorted(s Interface) bool algorithms on sortable

Slide 43

Slide 43 text

type Reader interface { Read(b []byte) (int, error) } type Writer interface { Write(b []byte) (int, error) } remember Reader and Writer?

Slide 44

Slide 44 text

func Fprintln(w Writer, ar ...interface{}) (int, error) func Fscan(r Reader, a ...interface{}) (int, error) func Copy(w Writer, r Reader) (int, error) algorithms on Reader and Writer

Slide 45

Slide 45 text

is this enough?

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

write generic algorithms on interfaces

Slide 48

Slide 48 text

“Be conservative in what you send, be liberal in what you accept” - Robustness Principle

Slide 49

Slide 49 text

a) func New() *os.File b) func New() io.ReadWriteCloser c) func New() io.Writer d) func New() interface{} what function do you prefer?

Slide 50

Slide 50 text

func New() *os.File

Slide 51

Slide 51 text

“Be conservative in what you send, be liberal in what you accept” - Robustness Principle

Slide 52

Slide 52 text

“Return concrete types, receive interfaces as parameters” - Robustness Principle applied to Go (me)

Slide 53

Slide 53 text

unless

Slide 54

Slide 54 text

Use interfaces to hide implementation details: - decouple implementation from API - easily switch between implementations / or provide multiple ones Hiding implementation details

Slide 55

Slide 55 text

context.Context

Slide 56

Slide 56 text

satisfying the Context interface context Context

Slide 57

Slide 57 text

satisfying the Context interface emptyCtx cancelCtx timerCtx valueCtx Context

Slide 58

Slide 58 text

interfaces hide implementation details

Slide 59

Slide 59 text

call dispatch

Slide 60

Slide 60 text

f.Do()

Slide 61

Slide 61 text

call dispatch Concrete types: static - known at compilation - very efficient - can’t intercept Abstract types: dynamic - unknown at compilation - less efficient - easy to intercept

Slide 62

Slide 62 text

type Client struct { Transport RoundTripper … } type RoundTripper interface { RoundTrip(*Request) (*Response, error) } interfaces: dynamic dispatch of calls

Slide 63

Slide 63 text

http.DefaultTransport http.Client

Slide 64

Slide 64 text

interfaces: dynamic dispatch of calls type headers struct { rt http.RoundTripper v map[string]string } func (h headers) RoundTrip(r *http.Request) *http.Response { for k, v := range h.v { r.Header.Set(k, v) } return h.rt.RoundTrip(r) }

Slide 65

Slide 65 text

interfaces: dynamic dispatch of calls c := &http.Client{ Transport: headers{ rt: http.DefaultTransport, v: map[string]string{“foo”: “bar”}, }, } res, err := c.Get(“http://golang.org”)

Slide 66

Slide 66 text

headers http.DefaultTransport http.Client

Slide 67

Slide 67 text

chaining interfaces

Slide 68

Slide 68 text

const input = `H4sIAAAAAAAA/7q+8vqc61Ovz1W4Pun61OtLrq+4vpALEAAA//+1jQx/FAAAAA==` var r io.Reader = strings.NewReader(input) r = base64.NewDecoder(base64.StdEncoding, r) r, err := gzip.NewReader(r) if err != nil {log.Fatal(err) } io.Copy(os.Stdout, r) Chaining interfaces

Slide 69

Slide 69 text

H4sIAAAAAAAA/7q+8vqc61Ovz1W4Pun61OtLrq+4vp ALEAAA//+1jQx/FAAAAA== io.Copy *gzip.Reader *base64.Decoder *strings.Reader *os.File סרפוג םולש

Slide 70

Slide 70 text

interfaces are interception points

Slide 71

Slide 71 text

- writing generic algorithms - hiding implementation details - providing interception points why do we use interfaces?

Slide 72

Slide 72 text

so … what’s new?

Slide 73

Slide 73 text

implicit interface satisfaction

Slide 74

Slide 74 text

no “implements”

Slide 75

Slide 75 text

funcdraw

Slide 76

Slide 76 text

package parse func Parse(s string) *Func type Func struct { … } func (f *Func) Eval(x float64) float64 Two packages: parse and draw

Slide 77

Slide 77 text

Two packages: parse and draw package draw import “.../parse” func Draw(f *parse.Func) image.Image { for x := minX; x < maxX; x += incX { paint(x, f.Eval(y)) } … }

Slide 78

Slide 78 text

funcdraw package draw package parse

Slide 79

Slide 79 text

funcdraw with explicit satisfaction package draw package common package parse

Slide 80

Slide 80 text

funcdraw with implicit satisfaction package draw package parse

Slide 81

Slide 81 text

Two packages: parse and draw package draw import “.../parse” func Draw(f *parse.Func) image.Image { for x := minX; x < maxX; x += incX { paint(x, f.Eval(y)) } … }

Slide 82

Slide 82 text

Two packages: parse and draw package draw type Evaler interface { Eval(float64) float64 } func Draw(e Evaler) image.Image { for x := minX; x < maxX; x += incX { paint(x, e.Eval(y)) } … }

Slide 83

Slide 83 text

interfaces can break dependencies

Slide 84

Slide 84 text

define interfaces where you use them

Slide 85

Slide 85 text

But, how do I know what satisfies what, then?

Slide 86

Slide 86 text

guru a tool for answering questions about Go source code.

Slide 87

Slide 87 text

http://golang.org/s/using-guru

Slide 88

Slide 88 text

the super power of Go interfaces

Slide 89

Slide 89 text

type assertions

Slide 90

Slide 90 text

func do(v interface{}) { i := v.(int) // will panic if v is not int i, ok := v.(int) // will return false } type assertions from interface to concrete type

Slide 91

Slide 91 text

func do(v interface{}) { switch v.(type) { case int: fmt.Println(“got int %d”, v) default: } } type assertions from interface to concrete type

Slide 92

Slide 92 text

func do(v interface{}) { switch t := v.(type) { case int: // t is of type int fmt.Println(“got int %d”, t) default: // t is of type interface{} fmt.Println(“not sure what type”) } } type assertions from interface to concrete type

Slide 93

Slide 93 text

func do(v interface{}) { s := v.(fmt.Stringer) // might panic s, ok := v.(fmt.Stringer) // might return false } type assertions from interface to interface

Slide 94

Slide 94 text

func do(v interface{}) { switch v.(type) { case fmt.Stringer: fmt.Println(“got Stringer %v”, v) default: } } type assertions from interface to interface

Slide 95

Slide 95 text

func do(v interface{}) { select s := v.(type) { case fmt.Stringer: // s is of type fmt.Stringer fmt.Println(s.String()) default: // s is of type interface{} fmt.Println(“not sure what type”) } } type assertions from interface to interface

Slide 96

Slide 96 text

Many packages check whether a type satisfies an interface: - fmt.Stringer : implement String() string - json.Marshaler : implement MarshalJSON() ([]byte, error) - json.Unmarshaler : implement UnmarshalJSON([]byte) error - ... and adapt their behavior accordingly. Tip: Always look for exported interfaces in the standard library. type assertions as extension mechanism

Slide 97

Slide 97 text

use type assertions to extend behaviors

Slide 98

Slide 98 text

Go Proverb Dave Cheney - GopherCon 2016 Don’t just check errors, handle them gracefully

Slide 99

Slide 99 text

type Context interface { Done() <-chan struct{} Err() error Deadline() (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface

Slide 100

Slide 100 text

var Canceled = errors.New("context canceled") errors in context

Slide 101

Slide 101 text

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} errors in context

Slide 102

Slide 102 text

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} errors in context

Slide 103

Slide 103 text

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context

Slide 104

Slide 104 text

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context

Slide 105

Slide 105 text

var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context

Slide 106

Slide 106 text

if tmp, ok := err.(interface { Temporary() bool }); ok { if tmp.Temporary() { // retry } else { // report } } errors in context

Slide 107

Slide 107 text

use type assertions to classify errors

Slide 108

Slide 108 text

Adding methods to an interface breaks backwards compatibility. type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(int) } How could you add one more method without breaking anyone’s code? type assertions as evolution mechanism

Slide 109

Slide 109 text

Step 1: add the method to your concrete type implementations Step 2: define an interface containing the new method Step 3: document it type assertions as evolution mechanism

Slide 110

Slide 110 text

type Pusher interface { Push(target string, opts *PushOptions) error } func handler(w http.ResponseWriter, r *http.Request) { if p, ok := w.(http.Pusher); ok { p.Push(“style.css”, nil) } } http.Pusher

Slide 111

Slide 111 text

use type assertions to maintain backwards compatibility

Slide 112

Slide 112 text

In conclusion

Slide 113

Slide 113 text

Interfaces provide: - generic algorithms - hidden implementation - interception points In conclusion

Slide 114

Slide 114 text

Interfaces provide: - generic algorithms - hidden implementation - interception points Implicit satisfaction: - break dependencies In conclusion

Slide 115

Slide 115 text

Interfaces provide: - generic algorithms - hidden implementation - interception points Implicit satisfaction: - break dependencies Type assertions: - to extend behaviors - to classify errors - to maintain compatibility In conclusion

Slide 116

Slide 116 text

what about generics?

Slide 117

Slide 117 text

Type Parameters A new draft was proposed to support generic programming in Go. It adds type parameters to: - functions - types

Slide 118

Slide 118 text

Type Parameters on functions func Print(type T)(vs []T) { for _, v := range vs { fmt.Print(v) } }

Slide 119

Slide 119 text

Type Parameters on types Interfaces are not always enough for container types: ● container/heap ○ Provides only high level functions (Push, Pop, etc) ○ Requires the user to implement the Heap interface ● container/ring ○ Provides a type Element containing a interface{} ○ Requires constant type conversions, loses compile time checks.

Slide 120

Slide 120 text

type Stack(type T) []T func (s Stack(T)) Push(v T) Stack(T) { … } func (s Stack(T)) Top() T { … } func (s Stack(T)) Pop() Stack(T) { … } func (s Stack(T)) Empty() bool { … } Type Parameters on types

Slide 121

Slide 121 text

is this enough?

Slide 122

Slide 122 text

func Max(type T)(vs []T) T { var max T for _, v := range vs { if v > max { max = v } } return max } A generic Max function

Slide 123

Slide 123 text

A generic Max function Max(int)([]int{1, 3, 2}) // 3 Max(float64)([]float64{1.0, 3.0, 2.0}) // 3.0 Max(string)([]string{"a", "c", "b"}) // "c" Max(bool)([]bool{true, false}) // ? Max([]byte)([][]byte{{1, 2}, {2, 1}}) // ?

Slide 124

Slide 124 text

type contracts

Slide 125

Slide 125 text

Type contracts provides a set of constraints on types. Sounds familiar? They look like functions: where each operation becomes a constraint. Type Contracts

Slide 126

Slide 126 text

contract comparable(v T) { var b bool = v < v } func Max(type T comparable)(vs []T) T { … } Max([]int)([]int{1, 3, 2}) // 3 Max([]string)([]string{"a", "c", "b"}) // "c" A comparable type

Slide 127

Slide 127 text

contract length(v T) { var n int = len(v) } func Length(type T length)(x T) int { return len(x) } Length([]int)([]int{1, 2, 3}) // 3 Length(map[int]int)(map[int]int{1: 1}) // 1 A type with length

Slide 128

Slide 128 text

int *os.File *strings.Reader *gzip.Writer []bool contract Reader contract Writer contract length contract comparable contract anything() {}

Slide 129

Slide 129 text

so … interfaces or contracts?

Slide 130

Slide 130 text

Interfaces and contracts define sets of types. Interfaces constrain on methods, contracts on anything. → all interfaces can be represented as contracts contract Stringer(x T) { type Stringer interface { var s string = x.String() String() string } } Interfaces vs Contracts

Slide 131

Slide 131 text

Interfaces constrain on methods, contracts on anything. → not all contracts can be represented as interfaces contract comparable(v T) { var b bool = v < v } contract comparable(v T) { var b bool = v.Equal(v) } Interfaces vs Contracts

Slide 132

Slide 132 text

putting the con back in contract

Slide 133

Slide 133 text

Contracts are great, but Once instantiated they are concrete types, so unlike interfaces: ● They hide no implementation ● They don’t provide dynamic dispatching This is has some good effects: ● the type system once compiled doesn’t change. ● there’s no change to the reflect package.

Slide 134

Slide 134 text

contracts are* too smart *probably

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

Remember the comparable contract? contract comparable(v T) { var b bool = v.Equal(v) } What is the type of the Equal method? ● func (v T) Equal(x T) bool ● func (v *T) Equal(x T) bool ● func (v T) Equal(x interface{}) bool contracts are probably too smart

Slide 137

Slide 137 text

Interfaces provide: - generic algorithms - hidden implementation - interception points Implicit satisfaction: - break dependencies Type assertions: - to extend behaviors - to classify errors - to maintain compatibility Contracts would provide: - generic algorithms - generic types Libraries for: - Concurrency patterns - Functional programming Remove duplication: - int, int32, int64 … float32, float64 … - bytes vs strings contracts vs interfaces

Slide 138

Slide 138 text

Conclusion Interfaces are amazing and I still love them <3 Type parameters are great! ● A library of concurrency patterns? ● Functional programming in Go? Contracts are interesting, but probably *too smart* for Go. I hope to see new iterations of the draft simplifying the concepts.

Slide 139

Slide 139 text

Go is about simplicity I’d argue contracts are not

Slide 140

Slide 140 text

!הדות Thanks, @francesc