Object Oriented Programming II

CIS 193 – Go Programming

Prakhar Bhandari, Adel Qalieh

CIS 193

Course Logistics

Interfaces

Interfaces are Contracts

All of the types we've seen so far are concrete types

Interface types are abstract types
- Doesn't expose the internal structure of its values
- Only exposes some methods

Don't necessarily know what it is, just what it can do

More concretely, an interface is a named set containing method headers

Interface Syntax

An interface looks like this

type Runner interface {
    Run(feet int)
}

Runner is an interface type

We can define interfaces using interfaces and methods

type SprintRunner interface {
    Runner
    Sprint(feet int) (distance int)
}

Typically end in "er"

Order doesn't matter for the methods inside

Example Interface

Here's an interface we've seen before

package fmt

type Stringer interface {
    String() string
}

The fmt package looks for types that satisfy Stringer and uses their String() method to print them

Interface Satisfaction

A type satisfies an interface if it has all of the methods the interface requires

func (c Celsius) String() string { 
    return fmt.Sprintf("%f°C", c)
}

Type Celsius satisfies Stringer

We say that Celsius is a Stringer

Interface Satisfaction

With interfaces, Type vs *Type matters when considering if a type has a method

func (c *Celsius) String() string { 
    return fmt.Sprintf("%f°C", *c)
}

var c Celsius = 4.5

Celsius does not satisfy Stringer here - what happens when we do fmt.Println(c)?

Interface Satisfaction

Only the methods revealed by the interface type may be called, even if the underlying concrete type has more

type Runner interface {
    Run(feet int)
}

type Gopher struct {
    name string
}
func (d Gopher) Bark(s string) { 
    fmt.Println(s)
}
func (d Gopher) Run(feet int) { 
    fmt.Printf("%s ran %d feet!\n", d.name, feet)
}

var d Runner
d = Gopher{"Henry"}
d.Run(23) // "Henry ran 23 feet!"
d.Bark("woof woof") // d.Bark undefined (type Runner has no field or method Bark)

Empty Interfaces

var anything interface{}

anything = 6
anything = "hello"
anything = false

This is how fmt.Println() (and other printing functions) can take any arguments

Non-empty interfaces are usually satisfied by a pointer type, especially with structs

Interface Values

How is an interface actually stored?

An interface has two components, type and value

var w Runner // value = nil, type = nil

w = Gopher{"Henry"} // value = Gopher{"Henry"}, type = Gopher

The type of an interface is the underlying dynamic type

Interface values can be compared using ==
- If both are nil, this returns true
- If both dynamic types are equal and both dynamic values are equal, this returns true

Why doesn't this work?

var x interface{} = []int{1, 2}
fmt.Println(x == x) // panic: comparing uncomparable types

Example: Sorting via sort.Interface

The sort package provides in-place sorting of any ordered sequence

package sort

type Interface interface {
    Len() int
    Less(i, j int) bool // i, j are indices of sequence elements
    Swap(i, j int)
}

Type Assertions

Syntax

x.(T)

Type assertions are operations applied to an interface value

If T is a concrete type:
- The type assertion checks if x's dynamic type is equal to T, panics otherwise
- Extracts the concrete value from x

For example:

var r Runner
r = Gopher{"Harry"}
a1 := r.(Gopher) // successful, a is now Gopher{"Harry"}
a2 := r.(Cat) // panic

Type Assertions

If T is an interface type:
- The type assertion checks whether x's dynamic type satisfies T - if yes, the result still has the same dynamic type and value, but the interface type is changed to T
- Commonly used to make a different (usually larger) set of methods available

// Gopher has two methods, Run and Bark
type Runner interface {
    Run(feet int)
}
type Barker interface {
    Bark(s string)
}

var r Runner
r = Gopher{"Harry"}
rb := r.(Barker)
rb.Bark("meow") // prints "meow"
rb.Run(23) // fails, rb is a Barker - no Run method

Type Assertions

Checking the type without having a panic

var r Runner
r = Gopher{"Harry"}
a1, ok := r.(Gopher) // a is now Gopher{"Harry"}, ok = true
a2, ok := r.(Cat) // a2 = nil, ok = false

Type Switches

How do we extract the value from an empty interface?

Bad way:

func HandleUnknown(x interface{}) {
    if _, ok := x.(int); ok {
        fmt.Printf("%d\n", x)
    } else if _, ok := x.(string); ok {
        fmt.Printf("%s\n", x)
    }
}

Good way:

func HandleUnknown(x interface{}) {
    switch x := x.(type) {
    case int:
        fmt.Printf("%d\n", x)
    case string:
        fmt.Printf("%s\n", x)
}

File I/O

Simple I/O with ioutil

import "io/ioutil"

func main() {
    // Read entire file
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // Write entire file
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

What's bad about loading the entire file into memory?

File I/O with bufio

Provides buffered I/O

Creating a read buffer

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // Open intput file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }

    // Read buffer
    r := bufio.NewReader(fi)
    ...
}

File I/O with bufio

Creating a write buffer

func main() {
    ...
    // Open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }

    // Write buffer
    w := bufio.NewWriter(fo)
    ...
}

File I/O with bufio

Copying a file

func main() {
    // []byte buffer for each chunk
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf) // Read a chunk
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // Write chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

More Functions

Variadic Functions

A function that can be called with varying numbers of arguments (eg: fmt.Println())

func product(vals ...int) int {
    prod := 1
    for _, val := range vals {
        prod *= val
    }
    return prod
}

vals is a slice here

If the arguments are already in a slice, place "..." after them

values := []int{1, 3, 4}
product(values...) // 12

Deferred Function Calls

An ordinary function or method call prefixed by the defer keyword

Evaluated when the statement is executed, but the actual call is deferred until the parent function is done

Multiple defers are executed in the reverse of the order in which they were deferred

Usually done with paired operations (eg: open/close) to make sure resources are released or some final action is always done

f, err := os.Open(filename)
if err != nil {
    return nil, err
}
defer f.Close()

Panic

Normal execution stops, all deferred functions are executed, and the program crashes with a log message

panic() accepts any value as an argument

Thank you

Prakhar Bhandari, Adel Qalieh

CIS 193