go: it's not just for google
DESCRIPTION
Another slab of Google Go madness with an emphasis on code and reflection.TRANSCRIPT
Goit’s not just for Google
Eleanor McHugh
http://slides.games-with-brains.net/
Rough Cut!
compiled
garbage collected
imperative
package main
import “fmt”
const HELLO string = “hello”var WORLD string = “world”
func main() { fmt.Println(HELLO, WORLD)}
strongly typed
value
boolean, numeric, array
value
structure, interface
reference pointer, slice, string, map, channel
function function, method, closure
underlyingtype
methodset
expressedtype
underlyingtype
methodset
expressedtype
embeddedtypes
package Integer
type Int int
func (i *Int) Add(x int) { *i += Int(x)}
type Buffer []Int
func (b Buffer) Swap(i, j int) { b[i], b[j] = b[j], b[i]}
func (b Buffer) Clone() Buffer { s := make(Buffer, len(b)) copy(s, b) return s}
func (b Buffer) Move(i, n int) { if n > len(b) - i { n = len(b) - i } segment_to_move := b[:i].Clone() copy(b, b[i:i + n]) copy(b[n:i + n], segment_to_move)}
package main
import “fmt”import "Integer"
func main() { i := Integer.Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b.Swap(1, 2) b.Move(3, 2) b[0].Add(3) fmt.Printf(“b[0:2] = %v\n”, b[0:2])}
produces:b[0:2] = [6 4]
testable
func (b Buffer) Eq(o Buffer) (r bool) { if len(b) == len(o) { for i := len(b) - 1; i > 0; i-- { if b[i] != o[i] { return } } r = true } return}
func TestSwap(t *testing.T) { i := Buffer{0, 1, 2, 3, 4, 5} b := i.Clone() b.Swap(1, 2) if !b[1:3].Eq(Buffer{2, 1}) { t.Fatalf("b = %v", b) }}
package Vectorimport . "Integer"
type Vector struct { Buffer}
func (v *Vector) Clone() *Vector { return &Vector{v.Buffer.Clone()}}
func (v *Vector) Slice(i, j int) Buffer { return v.Buffer[i:j]}
package Vectorimport "testing"
func TestVectorSwap(t *testing.T) { i := Vector{Buffer{0, 1, 2, 3, 4, 5}} v := i.Clone() v.Swap(1, 2) r := Vector{Buffer{0, 2, 1, 3, 4, 5}} switch { case !v.Eq(r.Buffer): fallthrough case !v.Buffer.Eq(r.Buffer): t.Fatalf("b[0:5] = %v", v) }}
include $(GOROOT)/src/Make.inc
TARG=integer
GOFILES=\ integer.go\ vector.go
include $(GOROOT)/src/Make.pkg
func BenchmarkVectorClone6(b *testing.B) { v := Vector{Buffer{0, 1, 2, 3, 4, 5}} for i := 0; i < b.N; i++ { _ = v.Clone() }}
func BenchmarkVectorSwap(b *testing.B) { b.StopTimer() v := Vector{Buffer{0, 1, 2, 3, 4, 5}} b.StartTimer() for i := 0; i < b.N; i++ { v.Swap(1, 2) }}
$ gotest -bench="Benchmark"rm -f _test/scripts.a6g -o _gotest_.6 integer.go vector.go nominal_typing_test.go embedded_typing_benchmark_test.go embedded_typing_test.gorm -f _test/scripts.agopack grc _test/scripts.a _gotest_.6 PASSinteger.BenchmarkVectorSwap200000000 8 ns/opinteger.BenchmarkVectorClone6 10000000 300 ns/op
dynamic
type Adder interface { Add(j int) Subtract(j int) Result() interface{} Reset()}
type IAdder int
func (i IAdder) Add(j int) { i[0] += i[j]}
func (i IAdder) Subtract(j int) { i[0] -= i[j]}
func (i IAdder) Result() interface{} { return i[0]}
func (i IAdder) Reset() { i[0] = *new(int)}
type FAdder []float32
func (f FAdder) Add(j int) { f[0] += f[j]}
func (f FAdder) Subtract(j int) { f[0] -= f[j]}
func (f FAdder) Result() interface{} { return f[0]}
func TestAdder(t *testing.T) { var a Adder
a = IAdder{0, 1, 2} a.Add(1) if i.Result().(int) != 1 { t.Fatalf("IAdder::Add(1) %v != %v", a.Result(), 1) } a.Subtract(2) if a.Result().(int) != -1 { t.Fatalf("IAdder::Subtract(2) %v != %v", a.Result()), -1 }
a = FAdder{0.0, 1.0, 2.0} a.Add(1) if a.Result().(float32) != 1.0 { t.Fatalf("FAdder::Add(1) %v != %v", a.Result(), 1.0) }}
reflected
package generalise
import "reflect"
func Allocate(i interface{}, limit... int) (n interface{}) { switch v := reflect.ValueOf(i); v.Kind() { case reflect.Slice: l := v.Cap() if len(limit) > 0 { l = limit[0] } n = reflect.MakeSlice(v.Type(), l, l).Interface()
case reflect.Map: n = reflect.MakeMap(v.Type()).Interface() } return}
package generalise
import . "reflect"
func Allocate(i interface{}, limit... int) (n interface{}) { switch v := ValueOf(i); v.Kind() { case Slice: l := v.Cap() if len(limit) > 0 { l = limit[0] } n = MakeSlice(v.Type(), l, l).Interface()
case Map: n = MakeMap(v.Type()).Interface() } return}
func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() return}
func TestAllocate(t *testing.T) { var s2 []int
s1 := []int{0, 1, 2} m := map[int] int{1: 1, 2: 2, 3: 3} switch { case throwsPanic(func() { s2 = Allocate(s1, 1).([]int) }): t.Fatal("Unable to allocate new slice")
case len(s2) != 1 || cap(s2) != 1: t.Fatal("New slice should be %v not %v", make([]int, 0, 1), s2)
case throwsPanic(func() { Allocate(m) }): t.Fatal("Unable to allocate new map") }}
func Duplicate(i interface{}) (clone interface{}) { if clone = Allocate(i); clone != nil { switch clone := ValueOf(clone); clone.Kind() { case Slice: Copy(clone, ValueOf(i))
case Map: m := ValueOf(i) for _, k := range m.Keys() { clone.SetMapIndex(k, m.MapIndex(k)) } } } return}
func TestDuplicateSlice(t *testing.T) { s1 := []int{0, 1, 2} var s2 []int
if throwsPanic(func() { s2 = Duplicate(s1).([]int) }) { t.Fatalf("Unable to duplicate slice %v\n", s1) } switch { case len(s1) != len(s2): fallthrough case cap(s1) != cap(s2): fallthrough case s1[0] != s2[0]: fallthrough case s1[1] != s2[1]: fallthrough case s1[2] != s2[2]: fallthrough t.Fatalf("Duplicating %v produced %v", s1, s2) }}
func TestDuplicateMap(t *testing.T) { m1 := map[int]int{1: 1, 2: 2, 3: 3} var m2 map[int]int
if throwsPanic(func() { m2 = Duplicate(m1).(map[int]int) }) { t.Fatalf("Unable to duplicate map %v\n", m1) }
switch { case len(m1) != len(m2): fallthrough case m1[1] != m2[1]: fallthrough case m1[2] != m2[2]: fallthrough case m1[3] != m2[3]: fallthrough t.Fatalf("Duplicating %v produced %v", m1, m2) }}
low-level
package raw
import . "reflect"import "unsafe"
var _BYTE_SLICE Type = Typeof([]byte(nil))
type MemoryBlock interface { ByteSlice() []byte}
func valueHeader(v reflect.Value) (Header *reflect.SliceHeader) { if v.IsValid() { s := int(v.Type().Size()) header = &reflect.SliceHeader{ v.UnsafeAddr(), s, s } } return}
func SliceHeader(i interface{}) (Header *SliceHeader, Size, Align int) { switch value := Indirect(ValueOf(i)); value.Kind() { case Slice: Header = (*SliceHeader)(unsafe.Pointer(value.UnsafeAddr())) t := value.Type().Elem() Size = int(t.Size()) Align = t.Align() case Interface: Header, Size, Align = SliceHeader(value.Elem()) } return}
func Scale(oldHeader *SliceHeader, oldESize, newESize int) (h *SliceHeader) { if oldHeader != nil { s := float64(oldESize) / float64(newESize) h = &SliceHeader{ Data: oldHeader.Data } h.Len = int(float64(oldHeader.Len) * s) h.Cap = int(float64(oldHeader.Cap) * s) } return}
func ByteSlice(i interface{}) []byte { switch i := i.(type) { case []byte: return i case MemoryBlock: return i.ByteSlice() }
var header *SliceHeader switch v := ValueOf(i); value.Kind() { case Interface, Ptr: header = valueHeader(v.Elem())
case Slice: h, s, _ := SliceHeader(i) header = Scale(h, s, 1)
case String: s := v.Get() h := *(*StringHeader)(unsafe.Pointer(&s)) header = &SliceHeader{ h.Data, h.Len, h.Len }
default: header = valueHeader(v) } return unsafe.Unreflect(_BYTE_SLICE, unsafe.Pointer(header)).([]byte)}
concurrent
package mainimport "fmt"
func main() { var c chan int c = make(chan int) limit := 16 go func() { for i := limit; i > 0; i-- { fmt.Print(<-c) } }() for i := limit; i > 0; i-- { select { case c <- 0: case c <- 1: } }}
produces:0110011101011010
func main() { var c chan int c = make(chan int, 16) go func() { for i := 16; i > 0; i-- { fmt.Print(<-c) } }() go func() { select { case c <- 0: case c <- 1: } }() for {}}
produces:0110011101011010
package generalise
type SignalSource func(status chan bool)
func Wait(s SignalSource) { done := make(chan bool) defer close(done) go s(done) <-done}
func WaitAll(count int, s SignalSource) { done := make(chan bool) defer close(done) go s(done) for i := 0; i < count; i++ { <- done }}
type Iteration func(k, x interface{}) bool
func (i Iteration) apply(k, v interface{}, c chan bool) { go func() { c <-i(k, v) }()}
func (f Iteration) Each(c interface{}) { switch c := ValueOf(c); c.Kind() { case Slice: WaitAll(c.Len(), SignalSource(func(done chan bool) { for i := 0; i < c.Len(); i++ { f.apply(i, c.Elem(i).Interface(), done) }}))
case Map: WaitAll(c.Len(), SignalSource(func(done chan bool) { for _, k := range c.Keys() { f.apply(k, c.Elem(k).Interface(), done) }})) }}
type Results chan interface{}
type Combination func(x, y interface{}) interface{}
func (f Combination) Reduce(c, s interface{}) (r Results) { r = make(Results) go func() { Iteration(func(k, x interface{}) (ok bool) { s = f(s, x) return true }).Each(c) r <- s }() return}
type Transformation func(x interface{}) interface{}
func (t Transformation) Transform(x interface{}) Value { return ValueOf(t(x))}
func (t Transformation) Map(c interface{}) (r interface{}) { r = Allocate(c) if i := MapIterator(r); i != nil { i.Each(c) } return}
func MapIterator(c interface{}) (i Iteration) { switch n := ValueOf(c); n.Kind() { case Slice: i = Iteration(func(k, x interface{}) bool { n.Elem(k.(int)).SetValue(t.GetValue(x)) return true })
case Map: i = Iteration(func(k, x interface{}) bool { n.SetMapIndex(ValueOf(k), t.GetValue(x)) return true }) } return}
func main() { s := []int{0, 1, 2, 3, 4, 5} d := Transformation(func(x interface{}) interface{} { return x.(int) * 2 } ).Map(s) sum := Combination(func(x, y interface{}) interface{} { return x.(int) + y.(int) }) fmt.Printf("s = %v, sum = %v\n", s, (<- sum.Reduce(s, 0)).(int)) fmt.Printf("d = %v, sum = %v\n", d, (<- sum.Reduce(d, 0)).(int))}
produces:s = [0 1 2 3 4 5], sum = 15d = [0 2 4 6 8 10], sum = 30
extensible
include $(GOROOT)/src/Make.inc
TARG=sqlite3
CGOFILES=\ sqlite3.go\ database.go
ifeq ($(GOOS),darwin)CGO_LDFLAGS=/usr/lib/libsqlite3.0.dylibelseCGO_LDFLAGS=-lsqlite3endif
include $(GOROOT)/src/Make.pkg
package sqlite3
// #include <sqlite3.h>import "C"import "fmt"import "os"
type Database struct { handle *C.sqlite3 Filename string Flags C.int}
func (db *Database) Error() os.Error { return Errno(C.sqlite3_errcode(db.handle))}
const( OK = Errno(iota) ERROR CANTOPEN = Errno(14))
var errText = map[Errno]string { ERROR: "SQL error or missing database", CANTOPEN: "Unable to open the database file",}
type Errno int
func (e Errno) String() (err string) { if err = errText[e]; err == "" { err = fmt.Sprintf("errno %v", int(e)) } return }
func (db *Database) Open(flags... int) (e os.Error) { db.Flags = 0 for _, v := range flags { db.Flags = db.Flags | C.int(v) } f := C.CString(db.Filename) if err := Errno(C.sqlite3_open_v2(f, &db.handle, db.Flags, nil)); err != OK { e = err } else if db.handle == nil { e = CANTOPEN } return}
func (db *Database) Close() { C.sqlite3_close(db.handle) db.handle = nil}
func Open(filename string, flags... int) (db *Database, e os.Error) { defer func() { if x := recover(); x != nil { db.Close() db = nil e = ERROR } }() db = &Database{ Filename: filename } if len(flags) == 0 { e = db.Open( C.SQLITE_OPEN_FULLMUTEX, C.SQLITE_OPEN_READWRITE, C.SQLITE_OPEN_CREATE ) } else { e = db.Open(flags...) } return}
fun!
finding out more
http://golang.org/
twitter://#golightly
http://github.com/feyeleanor/
wikipedia or google