Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Francesc Campoy Flores

April 15, 2017
Tweet

More Decks by Francesc Campoy Flores

Other Decks in Programming

Transcript

  1. understanding the interface
    @francesc

    View Slide

  2. what is an interface?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. what is a Go interface?

    View Slide

  10. abstract types
    concrete types

    View Slide

  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
    }

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. type ReadWriter interface {
    Reader
    Writer
    }
    union of interfaces

    View Slide

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

    View Slide

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

    View Slide

  20. interface{}

    View Slide

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

    View Slide

  22. View Slide

  23. why do we use interfaces?

    View Slide

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

    View Slide

  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?

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. Abstract Data Types

    View Slide

  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

    View Slide

  34. top(push( , ))=
    X S X

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  44. is this enough?

    View Slide

  45. View Slide

  46. write generic algorithms on interfaces

    View Slide

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

    View Slide

  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?

    View Slide

  49. func New() *os.File

    View Slide

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

    View Slide

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

    View Slide

  52. unless

    View Slide

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

    View Slide

  54. context.Context

    View Slide

  55. satisfying the Context interface
    context
    Context

    View Slide

  56. satisfying the Context interface
    emptyCtx cancelCtx timerCtx valueCtx
    Context

    View Slide

  57. interfaces hide implementation details

    View Slide

  58. call dispatch

    View Slide

  59. f.Do()

    View Slide

  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

    View Slide

  61. type Client struct {
    Transport RoundTripper

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

    View Slide

  62. http.DefaultTransport
    http.Client

    View Slide

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

    View Slide

  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”)

    View Slide

  65. headers http.DefaultTransport
    http.Client

    View Slide

  66. chaining interfaces

    View Slide

  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

    View Slide

  68. *strings.Reader
    H4sIAAAAAAAA/3qyd8GT3WueLt37fk/Ps46JT/d1vF
    w942nrlqezFzzZsQskMmfFi/0zX7b3cAECAAD//0G6
    Zq8rAAAA
    *base64.Decoder
    *gzip.Reader *os.File
    io.Copy
    你们好,我很高兴
    因为我在这里

    View Slide

  69. interfaces are interception points

    View Slide

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

    View Slide

  71. so … what’s new?

    View Slide

  72. implicit interface satisfaction

    View Slide

  73. no “implements”

    View Slide

  74. funcdraw

    View Slide

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

    View Slide

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

    }

    View Slide

  77. funcdraw
    package draw
    package parse

    View Slide

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

    View Slide

  79. funcdraw
    with implicit satisfaction
    package draw
    package parse

    View Slide

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

    }

    View Slide

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

    }

    View Slide

  82. interfaces can break dependencies

    View Slide

  83. define interfaces where you use them

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. the super power of Go interfaces

    View Slide

  88. type assertions

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  92. avoid abstract to concrete assertions

    View Slide

  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

    View Slide

  94. func do(v interface{}) {
    select v.(type) {
    case fmt.Stringer():
    fmt.Println(“got Stringer %d”, v)
    Default:
    }
    }
    runtime checks interface to concrete type

    View Slide

  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

    View Slide

  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

    View Slide

  97. use type assertions to extend behaviors

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  107. use type assertions to classify errors

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  111. use type assertions to maintain
    compatibility

    View Slide

  112. In conclusion

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  116. 谢谢
    Thanks, @francesc

    View Slide