Will contracts replace interfaces?

Will contracts replace interfaces?

Based on a previous talk named "Understanding the interface", in this talk I:
- discuss the pros and cons of interfaces,
- introduce the concepts from the generics draft for Go2 type parameters and contracts, and
- compare the two approaches.

D8e5d79ca42edc07693b9c1aacaa7e5e?s=128

Francesc Campoy Flores

February 11, 2019
Tweet

Transcript

  1. Will contracts replace interfaces? @francesc

  2. • Interfaces • Generics draft: ◦ Type Parameters ◦ Contracts

    • Interfaces vs Contracts Agenda
  3. what is an interface?

  4. "In object-oriented programming, a protocol or interface is a common

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

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

    means for unrelated objects to communicate with each other" - wikipedia
  7. None
  8. None
  9. None
  10. what is a Go interface?

  11. abstract types concrete types

  12. 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 }
  13. int *os.File *strings.Reader *gzip.Writer []bool

  14. 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
  15. type Reader interface { Read(b []byte) (int, error) } type

    Writer interface { Write(b []byte) (int, error) } two interfaces
  16. int *os.File *strings.Reader *gzip.Writer []bool io.Reader io.Writer

  17. type ReadWriter interface { Read(b []byte) (int, error) Write(b []byte)

    (int, error) } union of interfaces
  18. type ReadWriter interface { Reader Writer } union of interfaces

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

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

  21. interface{}

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

  23. None
  24. why do we use interfaces?

  25. - writing generic algorithms - hiding implementation details - providing

    interception points why do we use interfaces?
  26. 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?
  27. Cons: • how would you test it? • what if

    you want to write to memory? Pros: • ? a) func WriteTo(f *os.File) error
  28. 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
  29. Which ones does WriteTo really need? - Write - Read

    - Close b) func WriteTo(w io.ReadWriteCloser) error c) func WriteTo(w io.Writer) error
  30. “The bigger the interface, the weaker the abstraction” - Rob

    Pike in his Go Proverbs
  31. “Be conservative in what you do, be liberal in what

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

    you accept” - Robustness Principle
  33. Abstract Data Types

  34. 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
  35. top(push( , ))= X S X

  36. pop(push( , ))= S X S

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

  38. Axioms: top(push(S, X)) = X pop(push(S, X)) = S empty(new())

    !empty(push(S, X)) Example: stack ADT
  39. type Stack interface { Push(v interface{}) Stack Top() interface{} Pop()

    Stack Empty() bool } a Stack interface
  40. func Size(s Stack) int { if s.Empty() { return 0

    } return Size(s.Pop()) + 1 } algorithms on Stack
  41. type Interface interface { Less(i, j int) bool Swap(i, j

    int) Len() int } a sortable interface
  42. func Sort(s Interface) func Stable(s Interface) func IsSorted(s Interface) bool

    algorithms on sortable
  43. type Reader interface { Read(b []byte) (int, error) } type

    Writer interface { Write(b []byte) (int, error) } remember Reader and Writer?
  44. 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
  45. is this enough?

  46. None
  47. write generic algorithms on interfaces

  48. “Be conservative in what you send, be liberal in what

    you accept” - Robustness Principle
  49. a) func New() *os.File b) func New() io.ReadWriteCloser c) func

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

  51. “Be conservative in what you send, be liberal in what

    you accept” - Robustness Principle
  52. “Return concrete types, receive interfaces as parameters” - Robustness Principle

    applied to Go (me)
  53. unless

  54. Use interfaces to hide implementation details: - decouple implementation from

    API - easily switch between implementations / or provide multiple ones Hiding implementation details
  55. context.Context

  56. satisfying the Context interface context Context

  57. satisfying the Context interface emptyCtx cancelCtx timerCtx valueCtx Context

  58. interfaces hide implementation details

  59. call dispatch

  60. f.Do()

  61. call dispatch Concrete types: static - known at compilation -

    very efficient - can’t intercept Abstract types: dynamic - unknown at compilation - less efficient - easy to intercept
  62. type Client struct { Transport RoundTripper … } type RoundTripper

    interface { RoundTrip(*Request) (*Response, error) } interfaces: dynamic dispatch of calls
  63. http.DefaultTransport http.Client

  64. 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) }
  65. 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”)
  66. headers http.DefaultTransport http.Client

  67. chaining interfaces

  68. 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
  69. H4sIAAAAAAAA/7q+8vqc61Ovz1W4Pun61OtLrq+4vp ALEAAA//+1jQx/FAAAAA== io.Copy *gzip.Reader *base64.Decoder *strings.Reader *os.File סרפוג םולש

  70. interfaces are interception points

  71. - writing generic algorithms - hiding implementation details - providing

    interception points why do we use interfaces?
  72. so … what’s new?

  73. implicit interface satisfaction

  74. no “implements”

  75. funcdraw

  76. package parse func Parse(s string) *Func type Func struct {

    … } func (f *Func) Eval(x float64) float64 Two packages: parse and draw
  77. 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)) } … }
  78. funcdraw package draw package parse

  79. funcdraw with explicit satisfaction package draw package common package parse

  80. funcdraw with implicit satisfaction package draw package parse

  81. 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)) } … }
  82. 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)) } … }
  83. interfaces can break dependencies

  84. define interfaces where you use them

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

  86. guru a tool for answering questions about Go source code.

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

  88. the super power of Go interfaces

  89. type assertions

  90. 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
  91. func do(v interface{}) { switch v.(type) { case int: fmt.Println(“got

    int %d”, v) default: } } type assertions from interface to concrete type
  92. 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
  93. func do(v interface{}) { s := v.(fmt.Stringer) // might panic

    s, ok := v.(fmt.Stringer) // might return false } type assertions from interface to interface
  94. func do(v interface{}) { switch v.(type) { case fmt.Stringer: fmt.Println(“got

    Stringer %v”, v) default: } } type assertions from interface to interface
  95. 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
  96. 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
  97. use type assertions to extend behaviors

  98. Go Proverb Dave Cheney - GopherCon 2016 Don’t just check

    errors, handle them gracefully
  99. 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
  100. var Canceled = errors.New("context canceled") errors in context

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

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

    errors in context
  103. 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
  104. 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
  105. 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
  106. if tmp, ok := err.(interface { Temporary() bool }); ok

    { if tmp.Temporary() { // retry } else { // report } } errors in context
  107. use type assertions to classify errors

  108. 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
  109. 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
  110. 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
  111. use type assertions to maintain backwards compatibility

  112. In conclusion

  113. Interfaces provide: - generic algorithms - hidden implementation - interception

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

    points Implicit satisfaction: - break dependencies In conclusion
  115. 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
  116. what about generics?

  117. Type Parameters A new draft was proposed to support generic

    programming in Go. It adds type parameters to: - functions - types
  118. Type Parameters on functions func Print(type T)(vs []T) { for

    _, v := range vs { fmt.Print(v) } }
  119. 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.
  120. 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
  121. is this enough?

  122. 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
  123. 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}}) // ?
  124. type contracts

  125. Type contracts provides a set of constraints on types. Sounds

    familiar? They look like functions: where each operation becomes a constraint. Type Contracts
  126. 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
  127. 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
  128. int *os.File *strings.Reader *gzip.Writer []bool contract Reader contract Writer contract

    length contract comparable contract anything() {}
  129. so … interfaces or contracts?

  130. 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
  131. 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
  132. putting the con back in contract

  133. 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.
  134. contracts are* too smart *probably

  135. None
  136. 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
  137. 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
  138. 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.
  139. Go is about simplicity I’d argue contracts are not

  140. !הדות Thanks, @francesc