Martin Czygan Gopher June 8, 2017 18:00 CEST jenadevs meetup at Friedrich-Schiller-Universität Jena
- Gopher since 2013
- Programmer at Leipzig University Library
- Co-Author of Getting Started with Python Data Analysis
- Consultant
- Trainer at Python Academy
Open source projects: esbulk, solrbulk, microblob, gluish, metha, marctools.
Presentations at LPUG about pandas, luigi, neural nets.
Workshop on Go interfaces at Golab, an European Go conference in Italy.
BASIC, Pascal, Perl, Bash, Ruby, Java, C, C++, PHP, JavaScript, Python, Go.
First: slides
- Go: its users and critics, language constructs
- Go and OO, Go and Concurrency
- The Go development workflow
Then: hands-on, if you want:
- Get Go installed
- Write a simple (web service | concurrent program) in Go
From GoLang or the future of the dev:
From June 2017:
github.com/ksimka/go-is-not-good (1233 stars):
The list goes on and on:
- designed for stupid people
- no OOP
- no exceptions
- no versioning model
- too opinionated
- too simple
- I was curious about Ken Thompsons' experiment.
- I like production code and low operational overhead (e.g. install, maybe config, run).
- With Go, I mostly think about the problem, not about the language.
- 25 keywords
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
package main
import "fmt"
func main() {
fmt.Println("Hello 세계")
}
Play.
package main
import (
"fmt"
"time"
)
var timeout time.Duration
var N = 4
func main() {
n, k := N, 2.0
fmt.Printf("n=%d, k=%0.3f, timeout=%s", n, k, timeout)
}
Play.
- Lowercase private, uppercase public
package main
import "fmt"
func main() {
var a float64
var b int16
var c string
fmt.Printf("a=%v, b=%v, c=%v, len(c)=%v",
a, b, c, len(c))
}
Play.
type size in bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
package main
import "fmt"
const Prefix = ">> "
func main() {
for i := 0; i < 5; i++ {
log.Printf("%s %0d", Prefix, i)
}
}
Play. Break, continue as you expect.
package main
import "fmt"
func main() {
cities := []string{"Jena", "Weimar", "Erfurt"}
for i, city := range cities {
fmt.Println(i, city)
}
}
package main
import "fmt"
func Hello(name string) (string, error) {
if len(name) < 2 {
return "", fmt.Errorf("name too short")
}
return fmt.Sprintf("Hello %s", name), nil
}
func main() {
greeting, err := Hello("a")
fmt.Println(greeting, err)
}
Play.
package main
import "log"
func main() {
a, b := 4, 3
if a < b {
log.Println("a smaller b")
} else {
log.Println("a not smaller b")
}
}
Play.
default interface select
case defer go map struct
chan goto switch
fallthrough type
package main
import "fmt"
func main() {
s := "A"
switch s {
case "A":
fmt.Println("a")
case "B":
fmt.Println("b")
default:
fmt.Println("?")
}
}
Play.
- A design mistake correction from the C language
- Example, ascii85
var v uint32
switch len(src) {
default:
v |= uint32(src[3])
fallthrough
case 3:
v |= uint32(src[2]) << 8
fallthrough
case 2:
v |= uint32(src[1]) << 16
fallthrough
case 1:
v |= uint32(src[0]) << 24
}
Play.
interface select
defer go map struct
chan goto
type
- Defer can safe lots of code.
package main
func f() error {
defer fmt.Println("exiting f")
if rand.Float64() > 0.5 {
fmt.Println("f failed")
}
return nil
}
func main() {
f()
}
Play.
- Use cases: closing file, connections, response bodies, profiling
- make code much more readable, but has performance implications
interface select
go map struct
chan goto
type
interface select
go map struct
chan
type
package main
import "fmt"
func main() {
m := map[string]string{
"Meetup": "jenadevs",
"Location": "FSU Jena",
}
fmt.Println(m)
}
Play.
interface select
go struct
chan
type
- Concurrency: go, chan, select
- OO: type, struct, interface
- no classes
- composition over inheritance
- small interfaces
- no explicit declarations
- before we see compound types, let's look at something simpler
package main
import "fmt"
type Celsius float64
func main() {
var temp Celsius
fmt.Printf("below %v degree", temp)
}
Play.
package main
import "fmt"
type Celsius float64
func (c Celsius) String() string {
return fmt.Sprintf("%0.1f°", c)
}
func main() {
var temp Celsius
fmt.Printf("below %s degree", temp)
}
package main
import "fmt"
type Meetup struct {
Name string
Location string
}
func main() {
meetup := Meetup{
Name: "jenadevs",
Location: "FSU Jena",
}
fmt.Printf("%+v", meetup)
}
Play.
Compound types (play)
package main
import "fmt"
type Address struct {
City string
Street string
}
type Meetup struct {
Name string
Location Address
}
func main() {
meetup := Meetup{"jenadevs", Address{
Street: "Fürstengraben 1",
City: "Jena"}}
fmt.Printf("%+v", meetup)
}
type Client struct {
scheme string
host string
proto string
...
}
...
func (cli *Client) ContainerList(...) (..., error) {
...
}
- basic types (int, float, complex64, string, rune, byte, bool)
- slices (variable sized array)
- maps (hashmaps)
- struct types (compound types)
A few more builtin types:
- array types (fixed size)
- pointer types (pointers reference a location in memory where a value is stored rather than the value itself)
- function types (functions are first class objects)
- interface types (defined by a set of methods)
- channel types (typed conduits for communication and synchronisation)
- rarely used
package main
import "fmt"
func main() {
var v [3]int64
fmt.Println(v)
}
Play.
package main
import "fmt"
func main() {
var x = 42
fmt.Printf("%v", &x)
}
Play.
package main
import "fmt"
func main() {
x := new(int32)
fmt.Printf("%T", x)
}
Play.
You will see (use) pointer receivers on struct methods:
func (cli *Client) ContainerList ...
- required, if a method mutates values
- even if just a single method requires a pointer receiver, for consistency, all methods should use one
- lots of fun
- closures
package main
import "fmt"
func main() {
f := func(s string) string {
return fmt.Sprintf("<%s>", s)
}
fmt.Println(f("functional"))
}
Play.
package main
type Converter func(string) string
func Convert(value string, f Converter) string {
return f(value)
}
func main() {
// ...
}
Play.
type Converter func(string) string
Any function with the signature func(string) string
will work:
Play.
- set of methods
- satisfied implicitly
package main
type Starter interface {
Start() error
}
type Container struct {
ID string
}
func (c Container) Start() error {
// ...
}
...
Play.
The bigger the interface, the weaker the abstraction.
- https://youtu.be/PAAkCSZUG1c?t=5m18s - Go Proverbs, 2015
- Go has small interfaces
- Example: package io
type Reader interface {
Read([]byte) (n int, err error)
}
type Writer interface {
Write([]byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
Can small interfaces be useful?
- Explore IO workshop
... satisfied implictly. But that's actually not the most important thing about Go's interfaces. The really most important thing is the culture around them that's captured by this proverb, which is that the smaller the interface is the more useful it is.
io.Reader, io.Writer and the empty interface are the three most important interfaces in the entire ecosystem, and they have an average of 2/3 of a method.
package main
import "fmt"
func main() {
var x interface{}
x = 5
fmt.Printf("%v, %T\n", x, x)
x = "Hello"
fmt.Printf("%v, %T\n", x, x)
}
package main
import "fmt"
func IsString(v interface{}) bool {
_, ok := v.(string)
return ok
}
func main() {
fmt.Println(IsString(23))
fmt.Println(IsString("23"))
}
Play.
- via interfaces
- no explicit declaration
package main
import "fmt"
type Number struct{ x int }
func (n Number) String() string { return fmt.Sprintf("<Number %d>", n.x) }
func main() {
five := Number{5}
fmt.Println(five)
}
Play.
- no dependence between interface and implementation
- easy testing
- avoids overdesign, rigid hierarchy of inheritance-based OO
The source of all generality in the Go language.
(Requires some boilerplate, e.g. sort.Interface)
- based on Communicating Sequential Processes (CSP), 1978
- avoids explicit locks
Do not communicate by sharing memory; instead, share memory by communicating.
In Hoare's CSP language, processes communicate by sending or receiving values from named unbuffered channels. Since the channels are unbuffered, the send operation blocks until the value has been transferred to a receiver, thus providing a mechanism for synchronization.
Three elements:
- goroutines
- channels
- select statement
- the go keyword start a function in a separate lightweigth thread
package main
import (
"fmt"
"time"
)
func f() {
time.Sleep(1 * time.Second)
fmt.Println("f")
}
func main() {
go f()
fmt.Println("main")
time.Sleep(2 * time.Second)
fmt.Println("main")
}
Play.
- easy to start (many)
package main
import (
"fmt"
)
func main() {
N := 1000
for i := 0; i < N; i++ {
go func() {
x := 0
x++
}()
}
fmt.Println("done")
}
Play.
- How to communicate between goroutines? Enter channels.
- Channels: typed conduits for synchronisation and communication.
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello"
}()
fmt.Println(<-ch)
}
package main
// ...
func a(ch chan string) {
for msg := range ch {
fmt.Println(msg)
}
}
func main() {
ch := make(chan string)
go a(ch)
ch <- "Hello"
ch <- "World"
close(ch)
time.Sleep(1 * time.Second)
}
Play.
package main
import "fmt"
func main() {
c := make(chan string)
go func() {
c <- "Hello"
c <- "World"
}()
fmt.Println(<-c, <-c)
}
- select statement is similar to a switch but works with channels
The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case.
func main() {
ch := make(chan int)
go func() {
select {
case <-time.After(1 * time.Second):
log.Fatal("timeout")
case v := <-ch:
log.Println(v)
return
}
}()
time.Sleep(1100 * time.Millisecond)
ch <- 42
time.Sleep(1 * time.Second)
}
Play.
Commonly referred to a the Go tool. It runs code (compiles to a temporary file):
$ go run main.go
Before you build code, you should format Go code:
$ go fmt main.go
Or use: goimports - you favorite editor will have a suitable plugin.
Build statically-linked binary:
$ go build -o prog main.go
Cross complilation is easy.
Testing:
$ go test ./...
Testing and Benchmark helpers included.
Testing:
$ go vet ./...
Testing and Benchmark helpers included.
Get additional libraries:
$ go get github.com/fatih/structs
Dependency management is not great yet.
- net/http
- flag
- io
- xml, json
- archive
- encodings
- much more ...
- docker, k8s
- NES simulator
- https://github.com/gizak/termui
- https://github.com/peco/peco
- https://github.com/coreos/etcd
- https://github.com/chrislusf/seaweedfs
- https://github.com/minio/minio
- http://nsq.io/
A tiny bit about Docker.
List containers, create or remove them:
$ go run create.go
2017/06/08 15:31:47 Pulling from library/alpine
2017/06/08 15:31:47 Digest: sha256:0b94d1d1b5e ..
2017/06/08 15:31:47 Status: Image is up to date ..
2017/06/08 15:31:47 created: 0b2a66483 ...
hello world
2017/06/08 15:31:49 removing: 0b2a66483067c520b761 ...
$ go run list.go
sha256:a298d5ca31220b888b5 ...
sha256:a41a7446062d197dd4b ...
Example:
- hello world (hello)
- request a web page (fetch)
- concurrently request a list of web pages (fetchall)
- simple web service (server1, server2, server3)
Set $GOPATH
to ~/go
.
$ mkdir $GOPATH/src/github.com/user/hello
Create a file called hello.go
- insert code, save, run.
- request a web page with
http.Get
- concurrently request a list of web pages
- various servers
For dropping by and @dataduke and @jenadevs for organizing this meetup.