Introduction to Go

Martin Sulzmann

Overview

Hello World

package main

import "fmt"

var x int

func hi(y int) {
        fmt.Printf("hi %d\n",y)
}

func main() {
    x= 1
    hi(x)
    fmt.Printf("hello, world\n")
}

Printf similar to printf in C, see here for brief description of the C printf variant.

Go Toolchain

In most cases, our programs consist of a single file.

Simple form of type inference

package main

import "fmt"

func main() {
    var x int
    x = 1
    y := x + 1
    fmt.Printf("y = %d", y)

}

The type of y is inferred by the right-hand side.

Pretty convenient!

Languages like Haskell support full type inference where the types of functions can be inferred.

Control structures - for loop

The only control structure.

    for i := 0; i < 5; i++ {
        fmt.Printf("Value of i is now: %d \n", i)
    }

Infinite loop with break

    for {
        if j > 5 {
            break
        }
        fmt.Printf("Value of j is now: %d \n", j)
        j++

    }

Complete example

package main

import "fmt"

func main() {

    for i := 0; i < 5; i++ {
        fmt.Printf("Value of i is now: %d \n", i)
    }

    j := 0
    for {
        if j > 5 {
            break
        }
        fmt.Printf("Value of j is now: %d \n", j)
        j++

    }

}

Strings

Unlike in C, Go strings are not null terminated. In Go, strings are represented by a struct consisting of

  1. The length of the string.

  2. A pointer to the first byte.

These implementation details are hidden from the user. The user can make use of array notation to access a specific position in the string. There is a primitive len that computes the length of a string. Strings are immutable. This means they cannot be updated (but copied of course).

Next, we discuss a few examples that involve strings.

Printing a string.

    var x string = "Hello"
    y := "Hi"

    fmt.Println(x)
    fmt.Printf("%s \n", y)

Several ways to iterate over a string.

    for i := 0; i < len(x); i++ {
        fmt.Printf("%c", x[i])

    }

    for i, e := range x {
        fmt.Printf("%c", e)
        fmt.Printf("%c", x[i])

    }

// Don't care notation _
    for i, _ := range x {
        fmt.Printf("%c", x[i])

    }

    for _, e := range x {
        fmt.Printf("%c", e)

    }

Via range we obtain current position and element. The notation _ indicates that we don’t care about the value.

Arrays

Out of bounds check.

    var s1 [3]string
    s1[0] = "one"
    s1[1] = "two"
    s1[2] = "three"
    // s1[3] = "four"

Short-hand array initialization

    s2 := [3]string{"one", "two", "three"}

Iteration

    for index, elem := range s1 {
        fmt.Printf("%d %s \n", index, elem)
    }

Complete example

package main

import "fmt"

func main() {

    var s1 [3]string
    s1[0] = "one"
    s1[1] = "two"
    s1[2] = "three"

    for index, elem := range s1 {
        fmt.Printf("%d %s \n", index, elem)
    }

    s2 := [3]string{"one", "two", "three"}

    fmt.Printf("%s", s2[0])
}

Slices

More flexible arrays (length may change)

    s1 := make([]string, 3)

Short-hand

    s2 := []string{"a", "b"}

Functions on slices, e.g. append

    s2 := []string{"a", "b"}
    s3 := append(s2, "c", "d", "e")

Complete example

package main

import "fmt"

func printSlice(s []string) {
    for _, elem := range s {
        fmt.Printf("%s \n", elem)
    }

}

func main() {

    s1 := make([]string, 3)

    s1[0] = "one"
    s1[1] = "two"
    s1[2] = "three"

    printSlice(s1)

    s2 := []string{"a", "b"}

    s3 := append(s2, "c", "d", "e")

    printSlice(s3)
}

Call-by-value

Go uses call-by-value for paramter passing.

Call-by-reference can be emulated via pointers. Go supports pointers (but does not support pointer arithemtic).

Slices are internally represented via pointers.

Example

package main

import "fmt"

func inc(i int) int { return i + 1 }

func mapInc(xs []int) []int {
    for i, e := range xs {
        xs[i] = inc(e)
    }

    return xs
}

func mapInc2(xs []int) {
    for i, e := range xs {
        xs[i] = inc(e)
    }

}

func main() {

    xs := []int{1, 2, 3}
    ys := mapInc(xs)
    fmt.Printf("%d %d\n", xs[0], ys[0])
    mapInc2(ys)
    fmt.Printf("%d \n", xs[0])
}

Yields

2 2
3

Functions - Return values

func inc(i int) int
func myDiv2(x int, y int) (int, bool)
package main

import "fmt"

func inc(i int) int { return i + 1 }

// Direct reference to return values by name
// Only applies to tuple return values
func myDiv(x int, y int) (res int, status bool) {
    status = false
    if y == 0 {
        return
    }
    res = x / y
    status = true
    return
}

func myDiv2(x int, y int) (int, bool) {
    if y == 0 {
        return 0, false
    }
    return x / y, true
}

func main() {
    var res int
    var status bool
    res, status = myDiv(inc(3), 2)
    fmt.Printf("Result = %d \n", res)
    fmt.Printf("Status = %t \n", status)

    res, status = myDiv2(1, 0)
    fmt.Printf("Result = %d \n", res)
    fmt.Printf("Status = %t \n", status)
}

Strings again

We write a function to split a string into a prefix and a suffix once we see given byte.

func splitAt(x string, b byte) (string, string) {
    var r1, r2 []byte
    suffix := false

    for i, _ := range x {
        if !suffix {
            if x[i] == b {
                suffix = true
            } else {
                r1 = append(r1, x[i])
            }

        } else {
            r2 = append(r2, x[i])
        }
    }

    s1 := (string)(r1)
    s2 := (string)(r2)

    return s1, s2

}

We use slices r1 and r2 to collect the prefix and suffix. We check if we have seen the given element b yet, and then append elements in the original string to either r1 or 2. The element b is not added.

We require slices as append only operates on slices. As we wish to retrieve the result as strings, we need to include a type conversion.

Function splitAt returns two results, the prefix and the suffix. Multiple return results can be retrieved as follows.

    r1, r2 := splitAt("hel:lo", ':')

    fmt.Printf("%s %s", r1, r2)

Higher-order functions

Simple (increment) function.

func inc(i int) int { return i + 1 }

Functions can be arguments.

func apply(i int, f func(int) int) int {
    return f(i)
    }

func mapInt(f func(int) int, xs []int) []int {
    ys := make([]int, len(xs))
    for i, e := range xs {
        ys[i] = f(e)
    }

    return ys
}

func execute(f func()) {
    f()
}

Point to note:

Functions can be return values.

func hello()  {
    fmt.Print("Hello \n")
}

func hello2(x int) func() {
    fmt.Printf("%d ", x)
    return hello
}

Complete example

package main

import "fmt"

func inc(i int) int { return i + 1 }

func square(i int) int { return i * i }

func apply(i int, f func(int) int) int {
    return f(i)
}

func execute(f func()) {
    f()
}

func hello() {
    fmt.Print("Hello \n")
}

func hello2(x int) func() {
    fmt.Printf("%d ", x)
    return hello
}

// Create a new slice.
func mapInt(f func(int) int, xs []int) []int {
    ys := make([]int, len(xs))
    for i, e := range xs {
        ys[i] = f(e)
    }

    return ys
}

// In-place update. Result refers to xs.
func mapIntWithSideEffect(f func(int) int, xs []int) []int {
    for i, e := range xs {
        xs[i] = f(e)
    }

    return xs
}




func main() {
    execute(hello)

    execute(hello2(2))

    fmt.Printf("%d \n", apply(1, inc))

    xs := []int{1, 2, 3}
    ys := mapInt(inc, xs)
    zs := mapInt(square, xs)

    fmt.Printf("%d %d %d \n", xs[1], ys[1], zs[1])

}

Anonymous functions (lambdas)

Anonymous functions are function without a name. They originate from the lambda calculus and therefore we refer to anonymous functions also as lambda funtions.

Function definitions so far.

func inc(i int) int { return i + 1 }

Go supports anonymous functions (lambdas).

func (i int) int { return i + 1 }

Almost same syntax as for function definitions. We simply drop the function name.

How can we use lambda functions?

Instead of

ys := mapInt(inc, xs)

we can also write

ys := mapInt(func(i int) int { return i + 1 }, xs)

Advantage:

Consider

func hello3(x int) func() {
    return func () { fmt.Printf("Hello %d \n", x) }
}

Complete example

package main

import "fmt"

func apply(i int, f func(int) int) int {
    return f(i)
}

func execute(f func()) {
    f()
}

func hello() {
    fmt.Print("Hello \n")
}

func hello2(x int) func() {
    fmt.Printf("%d ", x)
    return hello
}

// Create a new slice.
func mapInt(f func(int) int, xs []int) []int {
    ys := make([]int, len(xs))
    for i, e := range xs {
        ys[i] = f(e)
    }

    return ys
}

func main() {
    execute(hello)

    execute(hello2(2))

    fmt.Printf("%d \n", apply(1, func(i int) int { return i + 1 }))

    xs := []int{1, 2, 3}
    ys := mapInt(func(i int) int { return i + 1 }, xs)
    zs := mapInt(func(i int) int { return i * i }, xs)

    fmt.Printf("%d %d %d \n", xs[1], ys[1], zs[1])

}

Partial function application, function types and function calls

Recall

func add(x int) func(int) int {
    return func(y int) int { return x + y }
}

Consider

    var f func(int) (func(int) int)

    f = add
    inc := f(1)
    fmt.Printf("%d", inc(3))

Consider

    x := (f(2))(3)
    fmt.Printf("%d", x)

Hence, we call this function with parameter 2

f(2) is an example of a partial function application (because we obtain another function as the result).

To avoid clutter, the convention adopted in Go (and other languages that support higher-order functions) is to allow removal of parentheses by assuming that

Compare

    var f func(int) (func(int) int)

    f = add
    x := (f(2))(3)

versus

    var f func(int) func(int) int


    f = add
    x := f(2)(3)

Both are the same!

func(int) func(int) int     =    func(int) (func(int) int)

    because function types are right-associative


f(2)(3)   =   (f(2))(3)

    because function application is left-associative

Complete example

package main

import "fmt"

func add(x int) func(int) int {
    return func(y int) int { return x + y }
}

func main() {
    var f func(int) (func(int) int)

    f = add
    inc := f(1)
    fmt.Printf("\n %d", inc(3))

    x := (f(2))(3)
    fmt.Printf("\n %d", x)

    var g func(int) func(int) int

    g = add
    y := g(2)(3)

    fmt.Printf("\n %d", y)
}

Currying versus uncurrying

Consider

func plus(x int, y int) int {
    return x+y
}

The arguments to plus must be both present.

In Go, it’s possible to ‘incrementally’ supply addition with its arguments.

func add(x int) func(int) int {
    return func(y int) int { return x + y }
}

Function add expects an integer argument (left operand) and yields a function. This function expects another integer argument (right operand) and then yields the expected result.

How to transform one function into the other? See below curry and uncurry functions.

“Curried style”

“Uncurried style”

Complete example

package main

import "fmt"

func plus(x int, y int) int {
    return x + y
}

func add(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

func curry(f func(int, int) int) func(int) func(int) int {
    return func(x int) func(int) int {
        return func(y int) int {
            return f(x, y)
        }
    }

}

func uncurry(g func(int) func(int) int) func(int, int) int {
    return func(x int, y int) int {
        return (g(x))(y)
    }
}

func main() {

    x1 := plus(1, 2)
    x2 := (add(1))(2)
    p := uncurry(add)
    x3 := p(1, 2)
    p2 := curry(plus)
    x4 := (p2(1))(2)

    fmt.Printf("%d %d %d %d", x1, x2, x3, x4)

}

Function closures

What’s the output of the following program?

package main

import "fmt"

type Type0 func()

func main() {
    var fn Type0
    var x int = 2

    fn = func() { fmt.Printf("%d \n", x) }

    fn()

    x = 3
    fn()
}

For each call to fn there is a different result!

Unlike pure functional programming languages, the Go language is impure. Pure means that for each function call and the same input arguments, we obtain the same output. This is also referred to as referential transparency.

Type definitions and structs (named types)

Type definitions

type myint int

Introduces a new type named myint. Values of type int and myint cannot be mixed.

The type myint is structurally isomorphic to int, so we can (type) convert one into the other.

Here is the complete example.

package main

import "fmt"

type myint int

func main() {
    var i int
    var j myint

    j = 1

    i = (int)(j)
    // i = j
        // yields a type error

    j = (myint)(i)

    fmt.Printf("%d", i)
}

Structs

type rectangle struct {
    length int
    width  int
}

Defines a named type rectangle that is structurally isomorphic to a struct of two ints where both ints can be accessed via field labels.

Construction of values.

    var r1 rectangle = rectangle{1, 2}
    var r2 rectangle = rectangle{width: 2, length: 1}
    r3 := rectangle{width: 2, length: 1}

Either by position or by field reference.

Intermediate Summary

func add(x int) func(int) int {
    return func(y int) int { return x + y }
}

// func add ...            Function declaration, I'm a function with name add
// func(int) int           Function type, from int to int
// func(y int) int { ...}  Anonymous function, lambda binder and body

Function application is left-associative.

(add(1))(2)   versus    add(1)(2)

Functions types are right-associative.

func(int) (func(int) int)    versus     f func(int) func(int) int

Connection to OO. In Go functions are first-class (that is, they can appear anywhere). This is similar to OO where objects are first-class.

For example, in an OO language, we can call a method m1 on some object o1. The result is an object on which we call another method m2.

o1.m1().m2()