Maps & Interfaces - Go Intro (III)

Maps

Maps in Go similar to Hashes in Ruby, Objects in JavaScript and Dictionaries in Python is a Key-Value datatype. Even though they are similar to sturcts, they differ in some things.

Both Keys and Values are statically types. Keys and Values can be of different types between them but all Keys on a Map should be of the same Type, same for Values.

Declare an empty Maps.

1
2
3
4
5
colors := map[string]string{
"red": "#ff0000",
"white": "#fffffff",
"black": "#000000",
}

Note that all of the values should have a comma at the end of the value.

1
2
3
4
// Another option #1
var color map[string]string
// Another option #2
color := make(map[string]string)

Add items to a map and the call it

1
2
3
4
// Set a alue for a key
colors["white"] = "#ffffff"
// Retrieve a value given a key
colors["white"]

Delete a value inside a map

1
delete(colors, "white")

Iterating over Maps

1
2
3
4
5
func main(){
for key,value := range colors {
fmt.Println("This is my ", key," and this is my value ", value )
}
}

Difference between Maps and Structs

Maps Structs
All Keys must be the same type Struct’s “Keys” (Fields) does not have a type
All Values must be of the same type Values can be of different types
Use to represent a collection of related properties Use to represent a “thing” with a lot of different properties
Don’t need to know all the keys at compile time You need to know all the different fields at compile time
Keys are indexed, we can iterate over them “Keys” (Fields) are not indexed, we can’t iterate a Struct
Referent Type Value Type

Interfaces 101

Interfaces theory

Interfaces are named collections of method signatures. If we have two or more Structs that have the have the same method, we can use an interface so we don’t need to specify the type of our Structs every time we need to work with them. We just use the interface in that case.

A code snippets from goyexample.com/interfaces leaves this very clear.

I added some notes as comments in the code

Here we declare our interface and other Structs that will make use of our interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Boilerplate code..

// This is our interface, there are many shapes that will have area and perimeter (rectangle, circle, triangle, ...)
type geometry interface {
area() float64
perim() float64
}

type rect struct {
width, height float64
}

type circle struct {
radius float64
}

Not that in the example above we distinguish two classes of types, an interface type “geometry” which can not be instantiated by itself and two concrete types “rect” and “circle” than can be instantiated. Other concrete types are the one predefines in go like “maps”, “structs”, “int” , etc.

Here’s an important part, and this might be a bit counter-intuitive if you come from POO languages. The way we have of implementing an interface is by implementing ALL the methods in the interface.

So our program now has a new type called geometry. So from now on, every single type in our program with a (receiver) function called *”area()”* that returns a float64 and a (receiver) function called *”perim()”* that returns a float64 are now an honorary member of type geometry.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// IMPORTANT !
// We implement an interface by just implementing ALLL the methods in the interface. Here we implement geometry on rect
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}

Making use of our interface.

1
2
3
4
5
6
7
8
9
10
11
12
13

func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}

func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}

As we can see, without interfaces we would have to make a measure() function for each type (rect and circle).

More “complex” Interfaces

Here’s an example of an interface that specifies multiples return and parameter types

1
2
3
type vehicle interface {
driveCoordinate(int, int) (int,int,string,error)
}

Interface inside interfaces

Example: https://golang.org/pkg/io/#ReadCloser

1
2
3
4
5
6
7
8
9
10
11
12
type Reader interface {
Read(p []byte) (n int, err error)
}

type Closer interface {
Close() error
}

type ReadCloser interface {
Reader
Closer
}

In order to satisfy the ReadCloser interface you will need to also satisfy both Reader and Closer interfaces.

Some notes on interfaces

  • Interfaces are not generic types
  • Interfaces are implicit
    • We don’t “inherit” or link any interface with other types, Go handles that for us
  • Interfaces are a contract to help us manage types and only types
  • Step #1 of understanding interfaces i understanding how to read them.

Examples of common interfaces

The io.Reader Interface

The Reader Interface can be found all over Go’s documentation, so I guess it is worth mentioning. This interface let us handle a wide diversity of input source with different types associated. The io.Reader interface represents an entity from which you can read a stream of bytes.

1
2
3
type Reader interface {
Read(buf []byte) (n int, err error)
}

Read reads up to len(buf) bytes into buf and returns the number of bytes read – it returns an io.EOF error when the stream ends.

Example of an HTTP request using the Read() function from the Reader interface:

1
2
3
4
5
6
7
8
9
10
// ...
func main() {
resp, err := http.Get("https://btc.lucascontre.site")
if err != nil {
fmt.Println(err)
}
bs := make([]byte, 9999)
resp.Body.Read(bs)
fmt.Println(string(bs))
}

We can see it reads from the body into our byte slice ([]byte)

The io.Writer Interface

1
2
3
type Writer interface {
Write(p []byte) (n int, err error)
}

Writer is the interface that wraps the basic Write method.

Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early.

The io.Copy function

The io.Copy function is, as well as io.Writer and io.Reader, part of the io packer

Copy definition:

1
func Copy(dst Writer, src Reader) (written int64, err error)

Copy copies from src to dst until either EOF is reached on src or an error occurs. It returns the number of bytes copied and the first error encountered while copying, if any.

With the io.Copy function, our http request would look like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
"fmt"
"io"
"os"
)

func main() {
resp, err := http.Get("https://btc.lucascontre.site")
if err != nil {
fmt.Println(err)
}
io.Copy(os.Stdout, resp.Body)
// We know that os.Stdout implement the Writer interface, because it has a Write() function
// We also know that resp.Body has a Read function because it implements the Read() function
}

Author: Lucas Contreras