Complexity is multiplicative.
-- Rob Pike
The Go Programming Language (golang) was conceived in 2007 by Robert Griesemer, Rob Pike and Ken Thompson, and officially announced in 2009. "Radical Simplicity" is the agenda of the design of the language.
Golang is
- a compiled language,
- typed (somewhat strongly),
- high-level,
- package system,
- garbage collection,
- first-class functions,
- portable,
- open-sourced,
- "batteries included" with a standard library and good tooling support.
Why are we even talking about another language here?!!
Hello world!
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":3001", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, ä˝ ĺĄ˝!")
}
Time to build it:
# read-compile-run
$ go run main.go
# or just read-compile
$ go build main.go
$ ./main
$ go clean
# or put the executable to a system location
$ go install main.go
$ main
$ go clean -i main.go
The benefit of hindsight
Must be, since it is from Ken Thompson, right?
Short answer: no. The good news though, they did learn from the past.
Remember this C mess?#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
With a bit help from typedef
, better.
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
Why can't we just read from left to right? Exactly.
// basic declaration
var i int
var arr [5]string
var hash map[string]float64
func(str string) int {
tmp := str // type inference inside function block
//
}
var foo func(string) int
// if we were to declare signal in golang
type Sigfunc func() Sigfunc
func signal(signo int, foo Sigfunc) Sigfunc {
//
}
BTW, no forward function declaration is necessary in golang.
Together with lexical scoping, first-class function has many interesting use cases. Closure is one example.func age(initAge int) func() {
i := initAge
return func() {
i++
fmt.Printf("%v years old\n", i)
}
}
func main() {
celBirthday := age(18)
for i := 0; i < 5; i++ {
celBirthday()
}
}
Being a functional programming language, Mathematica can easily do this as well.
makeAge[age_Integer] := Module[{year = age},
Print[++year]&
]
celBDay = makeAge[18];
Do[celBDay[], 5]
func add1(nPtr *int) int {
*nPtr++ // interpreted as (*nPtr)++
return *nPtr
}
var n int = 1
add1(&n)
Just a side note: in golang it's usually safe to return pointers from function calls.
int *foo(void) {
int i = 1;
return &i;
}
int *p = foo();
*p; /* compile error, if you are lucky */
func foo() *int {
i := 1
return &i
}
*foo()
a, ok := foo()
Let's bash C one more time...
```.c
/* confusing return value */
#include <string.h>
/* int strcmp(const char *str1, const char *str2) */
int main(void) {
/* define str1, str2 here */
if (!strcmp(str1, str2)) {
println("They are equal!");
}
}
/* another example: errno in UNIX programming */
#include <errno.h>
int foo(void) {
/* ... */
if (wrong()) {
errno = ENOENT;
return(-1);
}
return(0);
}
int main(void) {
int res = foo();
if (res < 0) {
log(errno);
}
/* another call may overwrite the errno */
foo2();
return(0);
}
Having multiple return values also gives golang a cleaner way for error handling.
func foo() (int, error) {
//
return 1, true
}
res, err := foo()
if err != nil {
// continue, if no error
}
Catch@Module[{conn},
conn = OpenSQLConnection["demo"];
Check[op1[], Throw["err1"]];
Check[op2[], Throw["err2"]];
(* ... *)
CloseSQLConnection[conn];
]
In golang, defer
can help with this.
func foo() {
conn := openConn()
defer conn.Close()
//...
}
Mathematica for instance, doesn't fully support the whole range of UTF-8 encoding.
FromCharacterCode[FromDigits["FFFF", 16], "UTF-8"] (* fine *)
FromCharacterCode[FromDigits["10000", 16], "UTF-8"] (* out-of-range *)
Golang uses interface
to generalize the behaviour of types. A concrete type satisfies an interface
implicitly.
In other words, don't check whether it IS-a duck: check whether it QUACKS-like-a duck, WALKS-like-a duck, etc, etc, depending on exactly what subset of duck-like behaviour you need to play your language-games with.
-- Alex Martelli
Other object-oriented languages have similar concepts. In Java for example:
interface Thief {
void steal(void);
}
// Person1 is a thief
class Person1 implements Thief {
void steal(void) {
//
}
}
// Person2 is not
class Person2 {
void steal(void) {
//
}
}
The "hello world" example is an example of using the interface.
// fmt.Fprint looks like this
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
//
}
// io.Writer is an interface defined as
type Writer interface {
Write(p []byte) (n int, err error)
}
// say we want to use fmt.Fprint for our purpose
type MyWriter struct{
filePath string
}
func (w *MyWriter) Write(p []byte) (n int, err error) {
f, err := os.Create(w.filePath)
if err != nil {
return 0, err
}
return f.Write(p)
}
// what's the meaning of interface{}
Interface embedding can be used to extend functionality.
type WLExpert interface {
knowWL()
}
type CloudExpert interface {
knowCloud()
}
type SW interface {
WLExpert
CloudExpert
knowNKS()
//...
}
###Concurrency
This is where golang really shines, I think.
`Goroutine` is what golang uses for running concurrent executions. One can regard it as similar to a native OS thread, only more lightweight.A sequential program runs in a single goroutine. Additional ones are launched by calling the go
statements.
./src/examples/goroutines/goroutines.go
func main() {
i := []int{1, 2}
go func() { i[0] = 3 }()
time.Sleep(1 * time.Second)
fmt.Printf("%v\n", i)
}
The previous example can get rid of the race condition using channel.
./src/examples/channels/channels.go
func main() {
i := []int{1, 2}
done := make(chan struct{})
go func() {
i[0] = 3
done <- struct{}{}
}()
<-done
fmt.Printf("%v\n", i)
}
The repo contains a more complete example (./src/examples/crawler/crawler.go
), a simple web link crawler, to illustrate how goroutines and channels fit together.
##Toolings
###Package system Golang is opinionated. One example is its package system.
./src/examples/testing/main.go
, ./src/examples/testing/helper/helper.go
// helper package
package helper
import "fmt"
var ExportedVar = 1
var secretVar = 2
func ExportedFunc() int {
fmt.Println("Exported")
return secretVar
}
func secretFunc() {
fmt.Println("You can't see me!")
}
// main package
package main
import (
"./helper"
"fmt"
)
func main() {
int := helper.ExportedFunc()
fmt.Println("we get", int, helper.ExportedVar)
}
Golang also makes it easy to work with version-controlled packages. For instance, the tool go get
can grab packages from common host sites such as GitHub, GitLab.
$ go get github.com/linsword13/goTour
###Cross compilation
go build
is all we need for generating binaries that work with other platforms.
# This gives all the supported OSs and architectures
$ go tool dist list
# This compiles the source to Windows x86-64
GOOS=windows GOARCH=amd64 go build source.go
A sample makefile can be found in ./src/examples/crawler/Makefile
.
###Other tools
Tool | Description |
---|---|
go build |
compile packages and dependencies |
go clean |
remove object files |
go doc |
show documentation for package or symbol |
go env |
print Go environment information |
go fix |
run go tool fix on packages |
go fmt |
run gofmt on package sources |
go generate |
generate Go files by processing source |
go get |
download and install packages and dependencies |
go install |
compile and install packages and dependencies |
go list |
list packages |
go run |
compile and run Go program |
go test |
test packages |
go tool |
run specified go tool |
go version |
print Go version |
go vet |
run go tool vet on packages |
go test
and go doc
are commonly used.
./src/examples/testing/helper/helper_test.go
package helper
import "testing"
func TestExportedFunc(t *testing.T) {
res := ExportedFunc()
if res != 2 {
t.Errorf("expect %v, instead got %v", 2, res)
}
}
# run the test
$ go test ./src/examples/testing/helper
# ask for function documentation
go doc fmt.Sprintf
And there are more. Profiling, benchmarking, documentation generation, text editor support..., all these come with the standard golang installation.
##Mathematica
Golang can generate C-style shared libraries. LibraryLink
or NETLink
can then be used to access these golang-generated libraries from Mathematica.
Here is the complete workflow of a trivial example.
-
Write up the implementation in golang.
./src/examples/mathematica/goSquare/main.go
package main import "C" //export GoSquare func GoSquare(n int) int { return n * n } func main() {}
-
Build the shared library.
# use .dylib on Mac go build -o ./goSquare.so -buildmode=c-shared ./goSquare
The Makefile can be found at ./src/examples/mathematica/Makefile
. With the shared library in place, NETLink can be used. If the platform doesn't support .net, LibraryLink can be a more portable solution.
-
Include the shared library in a C file, using LibraryLink's interface.
./src/examples/mathematica/callGoFunc.c
#include "WolframLibrary.h" #include "libgoSquare.h" DLLEXPORT int callGoFunc(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) { mint in; in = MArgument_getInteger(Args[0]); int res = GoSquare((int)in); MArgument_setInteger(Res, res); return LIBRARY_NO_ERROR; }
-
Final step can be done in Mathematica, using the CCompilerDriver package.
./src/examples/mathematica/example.nb
SetDirectory[NotebookDirectory[]]; Needs["CCompilerDriver`"] lib = CreateLibrary[ {"callGoFunc.c"}, "callGoFunc", "Debug" -> True, "IncludeDirectories" -> NotebookDirectory[], "Libraries" -> "goSquare" ]; callGo = LibraryFunctionLoad[lib, "callGoFunc", {Integer}, Integer] callGo[3] 9
Note: the above works fine on Linux, but I haven't got it to work on Mac yet. Golang's support for creating C-style dynamic library on Windows is being worked on.
##Summary There are some obvious lackings of golang, but overall it is a language with simplicity at its core and an ever-increasing community.
In case you want to read more: