understanding the interface

understanding the interface

Go interfaces are an essential part of the language, but many do not understand exactly when or how to use them.

In this talk, Francesc goes from the basic theory of interfaces to best practices, covering patterns usually seen on Go codebases.

D8e5d79ca42edc07693b9c1aacaa7e5e?s=128

Francesc Campoy Flores

April 15, 2017
Tweet

Transcript

  1. understanding the interface @francesc

  2. what is an interface?

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

    means for unrelated objects to communicate with each other" - wikipedia
  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. None
  7. None
  8. None
  9. what is a Go interface?

  10. abstract types concrete types

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

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

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

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

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

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

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

  20. interface{}

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

  22. None
  23. why do we use interfaces?

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

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

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

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

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

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

    you accept” - Robustness Principle
  32. Abstract Data Types

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

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

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

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

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

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

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

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

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

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

  45. None
  46. write generic algorithms on interfaces

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

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

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

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

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

    applied to Go (me)
  52. unless

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

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

  55. satisfying the Context interface context Context

  56. satisfying the Context interface emptyCtx cancelCtx timerCtx valueCtx Context

  57. interfaces hide implementation details

  58. call dispatch

  59. f.Do()

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

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

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

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

  66. chaining interfaces

  67. const input = `H4sIAAAAAAAA/3qyd8GT3WueLt37fk/Ps46JT/d1vFw942nrlqezFzzZsQskMmfFi/0zX7b3cAECA AD//0G6Zq8rAAAA` 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
  68. *strings.Reader H4sIAAAAAAAA/3qyd8GT3WueLt37fk/Ps46JT/d1vF w942nrlqezFzzZsQskMmfFi/0zX7b3cAECAAD//0G6 Zq8rAAAA *base64.Decoder *gzip.Reader *os.File io.Copy 你们好,我很高兴 因为我在这里

  69. interfaces are interception points

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

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

  72. implicit interface satisfaction

  73. no “implements”

  74. funcdraw

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

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

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

  79. funcdraw with implicit satisfaction package draw package parse

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

  83. define interfaces where you use them

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

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

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

  87. the super power of Go interfaces

  88. type assertions

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

    int %d”, v) Default: } } type assertions from interface to concrete type
  91. func do(v interface{}) { select 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
  92. avoid abstract to concrete assertions

  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{}) { select v.(type) { case fmt.Stringer(): fmt.Println(“got

    Stringer %d”, v) Default: } } runtime checks interface to concrete type
  95. func do(v interface{}) { select s := v.(type) { case

    fmt.Stringer: // s is of type int fmt.Println(s.String()) default: // t is of type interface{} fmt.Println(“not sure what type”) } } runtime checks interface to concrete type
  96. Many packages check whether a type satisfies an interface: -

    fmt.Stringer - json.Marshaler/Unmarhsaler - ... and adapt their behavior accordingly. 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 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. 谢谢 Thanks, @francesc