GO, sometimes called golang, is Google's language. It's most unique features are easy-to-create mutexs and producer/consumer queues, and aschycronous functions. It also has Duck-type polymorphism using only lightweight interfaces. Oddly, it doesn't have inheritance-based polymophism.
Surprisingly, GO uses C++ style explicit pointers and heap vs. stack object creation (instead of Reference/Value types). But it's garbage collected. We can't do without that. It's pretty funny to read GO explanations of pointers aimed at users who have only ever used Reference types.
Syntax-wise GO is C/Java-style. But it adds modern tricks -- optional line-endings, slices, implicit tuples -- and some very odd "we just wanted to be different" choices.
It's OK, I guess. But I wouldn't call it good.
GO borrows from new-style languages by requiring the block {}
's after loops and ifs, and removing the ()
's from their conditions. I don't really mind that. Semi-colons to end statements are optional. A weird side-effect of this is that breaking statements over multiple lines is delicate. They can only break in certain places. Ex's (using a declare):
var n int = 9 // declare and init n // breaking it like this is an error: var n int = 9 // but this way is fine: var n int = 9
I suppose it's not really a problem since modern editors immediately yell at you until you get the line-break right. And you shouldn't have so mnay long lines anyway.
Onto a really annoying rule: declaring unused variables, or unused anything, is an error. Bleh! This may be fine for finished programs, but it's horrible for writing and debugging. You're contantly having to comment and uncomment everything. GO's official hack/fix uses the dummy (underscore) variable: _=n
and _=f(2)
. Now we've "used" n and f. Oh -- including files you don't need? Also an error. This should be a compiler option, not a rule.
Declarations use var identifier type. Most langauges with this style use a colon, but not GO. Some simple declarations:
var n1 int var n2 string = "abc" // initialize var n3, n4 float32 // multiple vars
In practice, most of your declares will use the implicit type shortcut: leave out var
and use :=
instead of =. Ex's:
n:=6 // same as: var n int = 6 w:="cat" // var w string = "cat" n:=5 // ERROR -- := is a declaration; we can't redeclare n
GO has type int
but wants you to be specific with int32
and int16
and uint8
(byte). It doesn't have float or double -- you have to use float32 or 64.
A char
type doesn't exist in GO. Instead, characters are stored as unicode in uint32
's. With some casting, we can turn these into length-1 strings (again, GO has no char type). rune
is an alias for uint32 (the internet may tell you that rune is GO's version of char -- no, it's not).
GO gives us the "raw" string trick: a quoted string allows escape sequences, but one with backticks, like `a\nb`
, doesn't (it's 4 letters long). Because of this, GO programs tend to alternately use single or double quotes to write strings ("cat" or 'cat').
Everything in GO is auto-initialized (even members of structs). var n1 int
sets n1
to 0, strings are set to "", and so on.
GO gives us the modern multi-assign (both in and out of declarations):
var a,b int = 10,12 // a=10, b=12 // also legal for a non-declare assign: a,b=4,5 // a=4, b=5 // multi-assign with inferred-type declare: a,b := 4,2 // same as: var a,b int = 4,2 // size of list must match: a,b = callReturningThreeItems() // ERROR -- need to catch all 3 a,b,_ = callReturningThreeItems() // legal, using don't-care
The built-in Printf has the old C-like formatting: %4.2f
and so on. Added tricks are %v
as a stand-in for "any type" and %T
to print the type:
// formatting: fmt.Printf("val=%4.2f", 7.0) // 7.00 // %v is a wildcard: fmt.Printf("show me %v and %v", 5, "arf") // show me 5 and arf // get the type: fmt.Printf("type of n is %T", n) // type of n is int32
GO doesn't have mixed string and number math. You can't write "cat"+8
. This is a pain. The GO solution is the old string-print function:
// GO's version of n1+" and "+n2: w=fmt.Sprintf("%v and %v",n1, n2) // "5 and 9"
GO arrays are super primative. They can't change their size, which is fine. But the length is a part of the type. Really. A function might take type [5]string
, which means only size-5 arrays. That's a crazy limit. Examples of GO arrays:
var A [5]int // A is a length 5 array of ints aLen := len(A) // 5 (len gets the length) // proving length is part of the type: var A [3]int var B [5]int A=B // error: A has type [3]int, B has type [5]int // this sad function takes only a size-5 array as input: func XY(A [5]int) { ...
Array constants are written the usual way -- {a,b,c}. But a fun shortcut allows listing index:value pairs:
// basic array constant use: var A [3]int = [3]int{1,2,3} // using the implied-type shortcut: B := [5]int {3,6,7,2,5} // advanced index:value array constant: var C [6]string = [6]string {1:"cat", "rat" 4:"dog"} // C is {"","cat","rat","","dog",""}
As you'd guess, values not mentioned in the index:val syntax get the default values. But we also get the trick where "naked" values use successive indexes (1:"cat"
followed by just "rat"
). This is fun and seems readable.
The system can also figure out the size of constants, but it requires a new funny triple-dot [...] syntax:
// an array constant with a deduced size: A := [...]string{"hot","cold"} // the system fills in the [2] Nums := [...]int{4,9,12,67,-45} // becomes [5]int
GO's main list type is named slice. This is confusing since everyone else thinks a slice is a way to get a small read/write window into a list. Well, GO slices also do that. They do both. Whenever you see a GO slice you'll need to figure out which one it is, based on context. Yay!
As lists, the only slice operation we get is "add-to-end". For anything else we need slice tricks (remove-from-end is L=L[:len(L)-1]
). But using GO slices as traditional slices is just fine -- we get the usual operations.
Slices are declared as an array with no size, then grown using append. Example of a list-like slice:
Nums := []int{} // length 0 slice // basic add-to-end (from empty list): Nums=append(Nums, 3) // Nums is {3} Nums=append(Nums, 7,9,12) // adds all: {3,7,9,12}
Notice how append
isn't self-modifying. It can't be, since the list (which includes the list-pointer) is passed by value. We also can't have v.append(x)
since, well, we just can't. So we're stuck with this old-style append function.
We can't even directly add two slices. We need to use a trick where we unpack a slice into variadic (multiple) parameters, using dot-dot-dot:
N=append(N, []int{4,5,6}...) // same as append(N, 4,5,6)
If you were wondering, GO doesn't allow math-like list-concatentation like N+12 or N1+N2.
Growing a slice works the usual way -- when the backing array gets full it creates and copies into a new twice-as-big one. Creating one with make
allows you to specify the starting capacity (which can be found with cap(N)):
// a len-5 slice of strings, with capacity 20: s1 := make([]string, 5, 20) // testing: lenUsed, arrayLen := len(s1), cap(s1) // 5, 20
Part-of-list slices: We can also have traditional part-of-a-list slices. GO uses the usual slice index rule where you select using N[start:endPlusOne]. It allows leaving out the ends, like [:7]
, but doesn't have the shortcut where [:-2] means all but the last two. Examples of various slices:
// pretend N is [0,1,2,3,4,5,6] S = N[2:5] // {2,3,4} S = N[3:4] // {3} S = N[2:] // {2,3,4,5,6} S = N[:len(N)-1] // {0,1,2,3,4,5}
GO slices have 0-based index's. That's nice, because not all languages do that.
The point of a slice-like slice is being a read/write window, and it works just fine for that. S[0]=7
changes the underlying slice. But we have to remember not to use append on this type: it overwrites until the underlying array runs out of space, then jumps to a new array:
// N is the base, s1 is a slice from 1 to the end: N := []int{2,4,6,8} s1 := N[:3] // {2,4,6} // s1 knows it has capacity 4 (due to using N): fmt.Printn("len=%v cap=%v", len(s1), cap(s1)) // len=3 cap=4 // changing s1 also changes original N: s1[0]=9 // backing array N is now {9,0,0,0} // append is weird -- it knows N has more room: s1=append(s1, 5) // N is now {9,0,0,5} s1 = append(s1, 2) // no change to N (s1 uses new array)
Maps (hash tables) in GO work the usual way. The definition is strange, but it's consistant with the rest of GO:
var m1 map[string]int // type is map of string->int // NOTE: m1 is nil // assign using a map constructor: m1 = map[string]int {"cow":1, "dog":2} // cheap way to declare a usable empty map: m2 := map[string]int{} // {} constructs a map, with no items
You may also see people getting a usable empty map with m3:= make(map[string]int)
. It's just an alternate syntax.
Assigning to one works in the usual way: m1["frog"]=7
. Reading is also the usual way n := m1["frog"]
. But checking whether an entry exists is done in a novel, clever way -- GO optinally returns a tuple where the second value is a boolean does-it-exist value:
// official way to search for an item in a map: val, success = m1["frog"] // 7, true v1, e1 = m1["pony"] // 0, false (assuming no ponies) val = m1["pony"] // also legal (gives 0 if not found)
Normally you have to catch all returned values in a tuple, but GO map look-ups are an exception.
make
is re-used for when you want your new map to have a capacity:
// a map from string->float with room for 50 entries: m1 := make(map[string]float, 50)
Casting uses C-like syntax: TYPE(val), for example int(f)
(which drops the fraction).
As noted, GO has no character type, just ints. Examples:
// get a character from a sample string n := "a"[0] // it's not a char, just an int: fmt.Printf("type is %T, val=%v", n, n) // type is uint8, val=97 // rune is also an integer: var ch rune = "a" // ch is 97 // ...even when we try to use it like a char: fmt.Println("%v", "cat"+ch) // cat97
If we really need to display a rune as its actual character, we resort to casting it into a string: string(97)
, gives string "a".
Another fun fact, the shortcut types don't count as the same type as what they're shortcuts for. int
is (probably) int32
, but it counts as a different type:
var n1 int = 67 var n2 int16 = 89 var n3 int32 = 72 // int can't mix with int16 or int32, even though it is one: n1=n2; n1=n3 // ERROR's -- cannot convert ???? to int n1=int(n2); n1=int(n3) // use standard cast
The same goes for uint
mixed with uint16
and uint32
-- different types as far as casting goes
Every GO file begins, python-style, with that file's package name, like package catAndDog
. I think the files may also need to have the same name as the package, or maybe a popular GO tool requires this. I'm not sure.
GO include statements use the keyword import
. Things to include are listed in quotes, with the path (if needed):
import "fmt" // import 3 things in 1 statement: import ( // parens required "time" "sync"; "sync/atomic" )
A simple import merely finds the package. It doesn't put the contents in scope (i.e.: we need to find println using fmt.Println
). To pull the contents into your scope, add a dot before the name:
import . "fmt" Println("dot means we don't need leading fmt") fmt.Println("in fact, this is an error -- what's fmt?")
As mentioned before, importing something you don't use is an
error. GO has a useless fix for this -- add an underscore in
front of the filename: import _ "math/rand"
. You'd
think this means "import only if needed", which might actually be
useful, but no. It's a funny way to comment out an import.
The main entry point is C-style: func main()
, in a package named main. Command line args are a bit funny. You don't change the signature of main to accept arguments. Instead GO puts them in a special global named os.Arg
:
package main // official package name for entry point import "os" // needed to read from os.Args func main() { // no change, args are sent elsewhere numArgs := len(os.Args) progName :=os.Args[0] // as usual, 1st input is program name ...
Of course, GO doesn't have the namespace
keyword or anything like it, since it uses package scope rules. Files are GO's namespaces. But as seen before, items starting with a capital letter are public, while everything else is private to the package.
GO's has typedefs (keyword: type
) which is nice. Even better, a typedef'd basic type counts as a new type. This is how it should work -- define catCount
as an int and get errors when users improperly use dogCount's (or just ints) when you required a catCount
:
// simple typedef for an alternate float32: type loudType float32 // loadType and float32 don't mix: var x loudType = 32.6 // this is fine var f float32 = 45.2 x=f // ERROR -- float32 isn't loudType
We then get typical GO special rules for multiple typedef's in one line:
// pointless rule to define 2 at once (requires parens): type ( catWeight_t float32; catAge_t int )
Sadly, GO's reverses the "counts as different" rule for compound types. They count as the same, for example a new "slice of strings" type is identical to a slice of strings. That seems confusingly inconsistant:
// an alias for "slice of strings": type wordList []string W1 := wordList{"cat","dog"} // our new wordLists are identical to []string: var W2 []string = []string{"goat","stoat","boat"} W1 = W2 // wordList and []string feely mix
A work-around is adding a unique typedef for your basic type and making the compound typedef from that. Fun.
Go has another version of typedef where you never get a type mismatch error. This is done by adding an extra =
and is called an "alias":
type size_t=float32 // not a typedef -- an alias! // can freely mix size_t and float32
The docs say alias's are meant as a temporary hack for re-jiggerring programs. I suspect coders use it by accident in "real" code, mixing with the normal way, causing all sorts of confusion. Or maybe there's a "use of alias" warning.
GO's if's are mostly normal. Mostly. ()'s around the test are optional while {}'s around the block are required. Not a big deal. But moving into downright strange: an else
can't start a new line. The work-around uses the open-curly to start the line:
// simple ifs: if a==b { sameVal=true } if n==1 || n==4 || n==8 { n+=5 } // with an else: if (a<10) { w="small" } else { w="big" } // the "else" can't start a line
Go also gives us a new rule for getting a local-to-the-if variable: add a :=
declare before the test:
// n is a local var in this cascading if: if n:=cats+dogs; n>10 { fmt.Println("one %v\n", n) } else if !(n<20) { fmt.Println("two %v\n", n) } else { fmt.Println("three %v\n", n) }
In case you haven't seen this in other new languages, yes, this is a thing -- languages like adding special ways of making if and loop locals.
GO gives us the basic loops: for, while and foreach. As with GO's if-statement, the test has no parens and body's requires curly-braces are required. The for-loop looks normal:
for n:=0;n<10;n++ { fmt.Println(n) } // for loop
n:=0
is the only way to make a local variable -- the long way, var n int = 0
, isn't allowed.
While loops are also normal, but they re-use the FOR keyword:
for n<NUM { n*=2 } // while loop
The foreach loop also uses for
as the keyword. Better than most, it lets us easily get the index and the value, or just the index or just the value. The range
keyword makes it a foreach:
// an index and value foreach loop: for i,ch := range "abcdef" { ... } // i runs 0 to 5, ch runs 'a' through 'f' // index-only foreach: for i := range "abcde" { if i%2==0 ... // hack for value-only foreach: for _,ch := range "abcdef"
It makes a little sense to use "for" as a foreach, and some sense using "for" as a while. But deep down, you just know someone at Google said "hey, wouldn't it be cool if every type of loop used the same keyword?"
GO functions are suprisingly primative. They must begin with the keyword func
, which we realized was stupid 40 years ago. They can't be given optional parameters. They can't be overloaded. There are no template functions.
But otherwise, GO functions are normal. Syntax for a sample function:
func addsOne(n int) int { return n+1 }
The return type, which goes after, should be in parens, but that's optional when you have a single return value (so, yes, GO functions can return multiple values).
GO provides a fun shortcut for defining function parameters: multiple parameter declarations can share a type. Here a
and b
are both ints:
func sum(a,b int) int { return a+b }
As noted, GO functions can also return a tuple. In the prototype, write all of the types in parens, then return them using commas:
func sorted(a, b int) (int,int) { if a<b { return a,b } return b,a } n1,n2 := sorted(7,4) n1,n2 = sorted(46,83)
Note how tuples are created simply by listing the values with commas between. The usual tuple-parens aren't even allowed. Then note how receiving them is the same multiple-assign syntax. It's an extension of the a,b=3,8
rule. So that's nice.
As a silly shortcut, GO allows us to name the return values (in the header) and assign to those names; with a required empty return to send them. Here result
is the assigned-to return variable:
// "result" is declared in the header, simply assign to it: func maximum(n1, n2 float32) (result float32) { if n1>n2 { result=n1 } else { result=n2 } return // auto-returns our return-var "result" }
GO functions can return nothing. To do that, leave the return type empty (in other words, GO doesn't have or need a void
keyword):
func abc() { fmt.Println("abc") }
GO has no pass-by-reference. You'll see silly people claiming a leading star creates a PbR parameter (such as num *int
), but that's not a special GO rule; it's merely passing a pointer.
As a note, functions taking lists use slices (and not arrays, since they require preset sizes). Calling a slice-taking function with an array uses myArray[:]
to convert it to a slice:
func doThingWithList(L []string) string { ... } // slice-taking w := doThingWithList(myArray[:]) // call with an array
GO doesn't use the keyword class, preferring struct
. GO classes are typical, but with some odd changes. For one, all members are package scope (public in same file, else private). There's no private keyword and no way to make a member truely private. But you can make members globally public. It's not a keyword, instead, start their names with upper-case letters. That's a stupid choice for a C-like language.
GO also doesn't allow you to write constructors, but gives you 2 free ones. Out-of-the box, structs initialize everything to default values. Or you can use the free value-setting constuctor where you list all values: either annotated with names, or naked in the order they were declared. Constructor examples:
// a sample Point struct (a class): type Point struct { x,y float32 } // default-val constructor automatically used here: var p1 Point // x and y are auto-set to 0 // using 2nd free default constructor: p1 = Point{7,4} // matches 7 to x, 4 to y // ...or list fields by name: p1 = Point{y:5,x:9} // by name, any order p1 = Point{y:5} // as usual, x is 0
But you can't write your own constructors. You're expected to write normal functions instead (for example, instead of having a string-to-Point constructor Point("7,12")
, write a regular point-making function, such as makePoint("7,12")
).
GO fakes member functions in the standard way -- the "caller" is named and treated as a normal parameter inside of the function. This means there's no this
or self
in GO. It's the same system used by javascript, or C# extension functions. GO member functions are written outside of the class. The calling type is written as a special parameter in parens, in front of the function name. For example, this defines Point::toString:
type Point struct { x,y int } // the Point struct // define toString as a Point member function: func (p Point) toString() string { return fmt.Sprintf("(%v, %v)", p.x, p.y) }
We call this like p1.toString()
, but p1
becomes a normal parameter, copied into p
. We'll need to use p.x
and p.y
to examine "our" x and y members.
As a fun bonus rule, you can also write GO member functions for your typedefs, even ones for basic types:
type wholeNumber int // defines wholeNumber.isEven(): func (n wholeNumber) isEven() bool { return n%2==0 } cats wholeNumber := 7 evenCats := cats.isEven()
GO's member functions have a weird problem. Since the caller is passed in by value, we can't write self-modifying member functions. We're changing a local copy. This includes useful functions like p.setTo(5,8)
.
GO's solution is a special syntax to say "use the caller as a pointer". We don't pass it as a pointer, since that's not possible -- GO just does it behind the scenes. Add an extra star before the type:
// a working Point member function which correctly sets x: // v---add this star func (p *Point) setTo(newx int, newy int) { p.x = newx p.y=newy } // despite new syntax, call it as normal: p1.setTo(3,2)
There's no reason you can't just slap that star on every member function you write. But I think proper GO uses it to single-out just the self-modifying functions.
GO doesn't use inheritance for polymorphism (it uses Duck-typing), so GO inheritance is pretty simple, but syntactically unique: instead of writing the class(es) to inherit from on top, write them inside the {}'s. Here Sloth inherits from Climber (and has a normal string member, fur):
type Climber struct{ speed float32 } type Sloth struct { Climber fur string }
As usual, Climber's member vars are dumped directly into Sloth's. Sloth gets the s1.speed
and s1.fur
. But GO seems determinied to suck: to use the built-in constructor you need to navigate the nesting. Here we construct a Sloth; specifying "red" is normal, but to supply a speed of 0.52 we're required to say it comes from Climber:
// sadly, we need to specify the speed comes from Climber: var s1 Sloth = Sloth{Climber{0.52}, "red"}
Inheriting member functions works as we'd think. Of course the subclass inherits them. We can also rewrite them in the subclass to overwrite. There's no syntax overloading, since it's not needed. There's also none for virtual functions, since interface ducktyping assumes everything is virtual anyway.
GO also naturally allows multiple inheritance: simply list more than one class name to inherit from.
Like any modern language, GO has variables which can point to functions. Declaring, assigning and using them is straightforward. Then type inference makes it even simpler:
var fp func(int) int // declare fp = timesTwo // assign n := fp(7) // 14 // use // or implicit type: sd := timesTwo // declare
As function parameters, they look fine:
// search a list using arbitrary compare function: func find(W []string, w2 string, comp func(string,string) bool)
The syntax for anonymous functions is sneaky -- write them as normal functions and let GO figure out the anonymous part from context. Here we save an anonymous compare function in variable checkWord
:
checkWord := func(w string) bool { return len(w)==3 }
Since func
isn't the first thing, this is obviously an anonymous function. Just for fun, here's checkWord used in the standard GO Any
function, nothing funny is going on:
listHasLen3String := Any(myStringList, checkWord)
GO uses explicit pointers, the way a language written for adults should. No reference types here. The syntax is basic C-style: *int
is a pointer to an int, *p
dereferences, and &n
creates a pointer to n
. Ex's:
var n int = 8 // normal int var var p *int // pointer to an int p = &n // create pointer to n (*p)+=7 // dereference p=nil // sadly, GO uses "nil", not the superior null
GO adds the modern shortcut for pointers to members: dogPtr.name
implicitly follows the pointer. It's a shortcut for (*dogPtr).name
:
d1 := Dog{"spot",8} // a Dog (struct) dPtr := &d1 // pointer to a Dog (*dPtr).name+="ted" // official way to use dPtr.age+=1 // implicit dereference shortcut
Obviously, Go doesn't have the dPtr->age
dereference&member operation, since the dPtr.age
shortcut is the same but shorter.
GO has the standard new
operator. Of course, it's optional -- a struct can be local, or new'd to be a heap object. It also adds some shortcuts, so you won't actually type-out new
that often. new
examples:
var p1 *int // declare p1 as int-pointer, currently nil p1 = new(int) // new heap int, initted to 0 // declare& init a pointer to a size-5 heap array: W1 := new([5]string) // W1 points to {"","","","",""} on heap
Note the funny syntax where the type goes in parens. Of course, like all GO vars, the new item starts life initialized to default values. It turns out that's the only way to init them -- new
can't use the good constructor.
The shortcut for structs, arrays and slices is to replace new
with &
and omit the parens. Not much of a shortcut, except the good constructor is once again usable:
p1:=&Point{4,7} // p1 is a *Point to (4,7) // explicit declare (using the default (0,0)): var p2 *Point = &Point{} // pointer to a created len-5 string array: a1:=&[5]string{} // *a1 is {"","","","",""} // pointer to a len-5 slice {"","","","","cow"}: var s1 *[]string = &[]string{4: "cow"}
This trick doesn't work with basic types. You can't have np:=&6
or wp:=&string("abc")
.
GO then re-uses the ampersand to provide a confusing shortcut to return pointers from functions. Instead of new
, you can declare a local variable, return a pointer to it, and GO converts it on-the-spot to a heap variable. For example, this very basic function returns a pointer to a new integer:
func makeInt(n int) *int { n1 := n; return &n1 } // same as { n1:=new(int); *n1=n; return n1 } nPtr := makeInt(6) // nPtr is a pointer to heap value "6"
We especially like this trick with local structs, since we get to use the contructor. This function creates a heap Point:
func makeHeapPoint(x,y int) *Point { var p Point = Point{x,y} // a regular old local var return &p // GO safely moves p to the heap }
Returning functions, including functions with closures works just fine. Here's a basic curried add:
func curryAdd(x int) (func(int) int) { return func(y int) int { return x+y } } // using it: add4 := curryAdd(4) // add4 is a function that adds 4 n := add4(93) // n is 97
As usual, the returned function (func(y int) int { return x+y }
) just has x
bound to whatever it was when we made it (so 4, in our case)).
GO interfaces are how it does polymorphism, which is a big surprise, but mechnically GO interfaces don't have many surprises. As usual, they list the required function signatures. We leave off the func
keyword since it would be redundant. Here interface hasShow
is anything with a show() function and bigInterface is more:
type hasShow interface { show() } type bigInterface { computeSomething(float32, float32) float32 checkList([]int) bool }
Very surprisingly, GO uses "light" interfaces, where real classes don't need to mention what interfaces they think they support. Anything with a show()
can use the hasShow interface, no extra mark-ups required. Below Cat and Dog are also hasShow
's:
// 2 structs: type Dog struct { name string; age int } type Cat struct { name string; age int } // each has it's own version of show: func (d Dog) show() { fmt.Printf("dog %v is %v years\n", d.name, d.age) } func (c Cat) show() { fmt.Printf("Cat: %v, %v year\n", c.name, c.age) }
As usual, we can declare a hasShow
variable (an interface type) and assign and use it with either concrete type:
var ss hasShow c1:=Cat(); d1=Dog() ss=c1; ss.show() // runs the Cat show() ss=d1; ss.show() // again, but with a dog
Likewise we can send Cats or Dogs to a function expecting a hasShow
input:
func multiShow(hs hasShow, times int) { ... ... hs.show() // pass a Dog into the hasShow parm: multiShow( Dog{"abc",5}, 2) // or pass a Cat: var c1 Cat; c1.name="Princess"; c1.age=6 multiShow(c1, 4) // displays Princess 4 times
In short, GO interfaces work just like any other interface/inheritance system
In most languages, anything we want to act polymorphically needs to be a pointer. In GO, interfaces are magically both value and pointer types. This has the very minor benefit of making it a tad easier to declare interface variables. Then it has the drawback of being just plain confusing.
Here we declare ss1
as a regular variable (of an interface tyoe), but assign to it as if it were a pointer:
// declare interfaces as normal variables: var ss1 hasShow ss1 = &c1 // set to a pointer to a Cat ss1 = d1 // or assign directly as a value-type (to a Dog)
This is such as odd concept, and is so confusing that surely it must hold some hidden usefulness. It does not. And the rules get worse. Remember back in member functions how we need a different star-syntax for a self-modifying member function? Just in case, here's a quick review:
// Point class and 2 member functions: type Point struct { x,y int } func (p Point) toString() { ... } func (p *Point) changeX(newx int) { p.x = newx }
GO says that if a class has any star-type members then it must be assigned as a pointer -- like ss1=&varName
. Otherwise, you can choose (which I assume means to never use & unless you have to?)
It seems odd to see this under interfaces, but dynamic casting is for when you have polymorphism and interfaces are the way GO does that. The direct check "is this actually type X?" looks like: interfaceVariable.(
typeToConvertTo)
. GO-style, it returns the casted object plus the success (and, again, failure gives the default value, even contructing a default struct if needed). Ex's of casting a hasShow
into a Cat or Dog:
// this hasShow actually points to a Cat: var s1 hasShow s1 = Cat{"felix",7} // cast into Cat works: c1,b := s1.(Cat) // Cat{Felix,7}, true // cast into Dog is legal, and fails: d1 := s1.(Dog) // Dog{"",0}, (uncaught false)
An example of casting inside an IF with the local assign trick:
func takesAny(v interface{}) { if cc,success:=v.(Cat); success { // do stuff with the Cat, cc }
We also get a special type decoder for switches. Write the actual word "type" in the parens. This checks whether v
is a Cat or Dog or something else:
switch v.(type) { case Cat: ... case Dog: ... default: ... }
go
function keywordThis is a suposed strength of GO, having easy-to-use built-in asychronous features.
Adding leading keyword go
to a function call makes it asynchronous -- the caller won't wait for it to finish. Obviously, that only matters for a blocking function. And, of course, we call also call them without go
, causing us to wait. Here we call slowPrint
-- which blocks using Sleep
-- with and without go
:
// a function which blocks (for a second): func slowPrint(w string) { time.Sleep(Time.Duration(1000000000) // one second fmt.Println(w) } // testing it: go slowPrint("sp1") // we immediately move to next line fmt.Println("A") slowPrint("sp2") // wait 1 second for this to finish fmt.Println("B") // output: A (one second delay) sp1 sp2 B
The input to Sleep is of type time.Duration
, which is really just uint64
. The units are nanoseconds (one billionths of a second). That's why we need 1000000000 to get a 1-second delay. But GO defines time.Millisecond
as a million (the number of nanoseconds in a millisecond), letting us write time in the more familiar milliseconds: Sleep(1000*time.Millisecond)
.
Since we're on time, to get intervals GO has time.Now()
and time.Since(TIME_VAR)
. For example. this loop runs 40 times over 4 seconds:
t1 := time.Now() // loop runs until 4 seconds have passed: for time.Since(t1) < time.Duration(4000000000) { fmt.Println(int64(time.Since(t1))) // 100,000,000 fmt.Println(time.Since(t1)) // 100ms time.Sleep(100*time.Millisecond) }
GO has two sorts of auto-blocking communication variables, which it calls channels. The simplest is a buffer. Filling it is instant, unless it's full -- they you wait. Reading from it is the same, except you only wait when it's empty. A basic buffer.
GO uses make
to create them. Give it any size, even 1. Add and remove items using the special <- syntax:
// a new buffer with max size 10: syncInt := make(chan int, 10) // fill it with 64 and -47 (no waiting, yet): syncInt <- 64 syncInt <- -47 var n = <-syncInt // pulls out the 64
Note this is the same make
which can create slices and maps. GO chooses weird things to overload.
The other type of channel forces a synch between the sending and reading threads. They're like size 1 buffers, but more strict. Obviously, readers need to wait until someone writes something. But now writing to it makes you wait until someone else wants to read. It's like talking in person instead of leaving a note. The syntax is make
, but with no size.
In this example, a thread attempts to write to an empty channel, but is forced to wait until the reader is ready:
// sends an int to a channel then tells us: func slowSend(intChan chan int, sendMe int) { intChan <- sendMe // blocks until someone tries to read! fmt.Println("sent") } // start our function sending us a 64: iChan := make(chan int) go slowSend(iChan, 64) fmt.Println("this prints first -- function is blocking on send") // wait 5 seconds before reading it: time.Sleep(5000*time.Millisecond) num := <- iChan fmt.Printf("this probably come BEFORE the function prints")
Of course, you can put these in functions and call them with go
.
GO has a simple general-purpose mutex. You create variables of that type, and use them for whatever you want. When you call lock
it either locks it and moves on, or waits for it to be free if it's already locked (which is basic mutex stuff). Here's an example where we cause an anonymous function to wait on mutex waiter
while we pause for 2 seconds before unlocking it:
var waiter sync.Mutex // a mutex // lock it now, for fun: waiter.Lock() // call anon function which also uses waiter: go (func() { waiter.Lock() // will wait here for main to Unlock fmt.Println("abc") waiter.Unlock() })() // let the function run after 2 seconds: time.Sleep(time.Second*2) waiter.Unlock()
GO provides some shortcut mutexs, in the atomic
package (inside of sync
). As we know, simply adding to a number is a load and store, which could be interrupted, so requires a mutex. Instead of making everyone use a mutex to change a number, we get an atomic add. atomic.AddInt32(&n, 3)
quickly locks n
somehow, adds 3, then unlocks it. atomic
also provides a LoadInt32
function for weird set-ups where loading a value might be interrupted.
I feel like in practice, a class will be playing with a few members and you'd just lock write access with a mutex. But I suppose atomic.AddInt allows you to be more granular?
New languages need an "any" object type. Instead of having a list of some base class or interface, you have a list of "any" objects. Of course, you can't use them for anything without manually checking the type and branching. I don't get the appeal, but GO does, and has one of these.
Describing the "any" object is clever -- use an empty interface (interface{}
). Everything confirms to that, so you can assign anything. For example:
// "any" type n can be an int: var n interface{} // n has type "any" n = 7 n = "cow" func takeAnyTypeOfInput(n interface{}) { ... // array of any types (interface{} is the type): var AA [5]interface{} = [5]interface{}{}
Actually using an "any" type is obviously done through dynamic casting. Note that you're allowed to attemtp to cast into anything (int or string) -- the rule is the original variable must be an interface. Below we search a list of "any"'s, looking for ints and Cats:
func(L []interface{}) int { sum:=0 // sum of all integers and cat ages: for _,v := range L { if num,b := v.(int); b { sum+=num } if cc,b := v.(Cat); b { sum+=cc.age } } return sum }
GO has a linked-list class, but it's painful to use. Since GO has no template system, it only holds generic objects (interface{}
). It also doesn't work with the special foreach. You also need to call a set-up function since GO has no constructors:
import "container/list" var L1 *list.List // work best as pointers L1 = list.New() // creates and returns a heap linkList L2 := list.New() // the easy way
We get the usual pushback and pushfront which, of course, return a pointer to the element they create:
L1.PushBack("cow") e1 := L1.PushBack("bear") var e2 list.Element = L1.PushFront("ant")
Once we have an element, we can use InsertBefore, or Remove. We even get functions to move an element to the front, back or next to another element. But without templates, we don't get any sort of Find operations -- we can't search for the word "cow".
These also don't work with GO's foreach. Linked-list loops need to do things the long way:
for e:=L.Front(); e!=nil; e=e.Next() { Println(e.Value) }