Go for Pythonistas

Menno Smits

Python vs Go - what's the same?

  • strongly typed
  • relatively concise and expressive
  • automatic memory management
  • first class functions
  • closures
  • built-in dictionary type (called "map")
  • built-in list type (called "slice")
  • duck typing
  • modules (called "packages")

Python vs Go - what's different?

Python Go
dynamically typed statically typed
interpreted compiled
whitespace curly braces with strict layout
Objects with inheritance Structs with embedding and interfaces
pure pragmatic

What's it look like?


package main

import "fmt"

func main {
    fmt.Println("Hello NZPUG")
}
          

A more complex example


package main

// No such thing as "from foo import bar"
import (
     "bufio"
     "fmt"
     "io"
     "os"
)

// Note how the type comes after arg name in argument list
func countWords(source io.Reader) (map[string]int, error) {
     counts := make(map[string]int) // What's this?
     scanner := bufio.NewScanner(bufio.NewReader(source))
     scanner.Split(bufio.ScanWords)
     for scanner.Scan() {
          counts[scanner.Text()]++  // has ++ but it's not crazy like C
     }
     // returning errors is a common pattern
     return counts, scanner.Err()
}

func main() {
     counts, err := countWords(os.Stdin) // type inference!
     if err != nil {
          fmt.Fprintln(os.Stderr, err)
     }

     for word, count := range counts {  // "for" does all types of loops
          if count > 1 {
               // print anything, just like Python
               fmt.Println(word, count)
          }
     }
}

Error handling

  • WTF?! No exceptions!?
  • Explicit error handling instead: if err != nil ...
  • The 1970's called and they want their language back...
  • At least there's panic and defer

Objects?

Structs!

(with methods)


type Rectangle struct {
    length, width int
}

func (r *Rectangle) Area() int {
    return r.length * r.width
}
          

Inheritance?

No: composition and delegation!
(Embedding with syntactic sugar)


package main

import "fmt"

type Person struct {
     FirstName string
     LastName  string
}

func (p *Person) FullName() string {
     return p.FirstName + " " + p.LastName
}

func (p *Person) Intro() string {
     return p.FullName()
}

type Man struct {
     Person
}

func (m *Man) Intro() string {
     return "Mr " + m.LastName
}

func main() {
     p := Person{"Some", "Human"}
     fmt.Println(p.FullName(), ":", p.Intro())

     m := Man{Person{"John", "Smith"}}
     fmt.Println(m.FullName(), ":", m.Intro())

}
          

Output:

Some Human : Some Human
John Smith : Mr Smith
            

Performance

  • native code
  • efficient value storage and handling (like C)
  • transparent inlining
  • escape analysis (avoids unnecessary heap usage)
  • goroutines (more efficient than thread or process switching)
  • dynamic segmented stacks

Concurrency

  • Goroutines are cheap and easy
  • Asynchronous message passing using channels
  • Managed by the Go runtime
  • Scales well

Juju easily has 1000's of concurrent Goroutines per host


package main

import "fmt"

func produce(c chan int) {
     for i := 0; i < 10; i++ {
          c <- i
     }
     close(c)
}

func consume(c chan int, done chan bool) {
     for i := range c {
          fmt.Println(i)
     }
     close(done)
}

func main() {
     c := make(chan int)
     done := make(chan bool)
     go produce(c)
     go consume(c, done)
     <- done
}
        

Large Teams / Projects

  • super fast compilation
  • small language
  • simple modularisation concepts
  • static typing does help
  • good practices baked-in
    • variation from the One True Layout Style is a compile error
    • unused vars or imports are compile errors
  • Go command line tool
    • zero config build system
    • install
    • package installation
    • test framework
    • and much more

Go tool


# Installing a program or external package
go get github.com/foo/bar

# Make some changes...
cd $GOPATH/src/github.com/foo/bar
# edit, edit, edit

# Run all the tests
go test ./..

# Build and install again
go install ./..
          

Ease of deployment

A build gives you a statically linked binary (almost).

$ ldd wordcount
    not a dynamic executable
$ ldd juju
     linux-vdso.so.1 =>  (0x00007ffff9755000)
     libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f14096d5000)
     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f140930f000)
     /lib64/ld-linux-x86-64.so.2 (0x00007f1409915000)
          

Just copy the file and you're done.

Where Python wins

  • Expressability (low "concept to code" impedence)
  • Elegance
  • Conciseness
  • More mature ecosystem
    • Bigger community
    • Many more packages