Skip to content

Instantly share code, notes, and snippets.

@shilpavijay shilpavijay/GO - Getting started.md Secret
Last active May 8, 2018

Embed
What would you like to do?
GO

Concurrency is when two or more tasks make progress in simultaneously. It could be very tedious to get it right in most of the programming languages. GO however has a rich support to concurrency and makes the task very easy.

Goroutines:

Goroutines are functions or methods that can run concurrently with other methods. They are light-weight threads. The cost of creating a goroutine is very small compared to that in creating an actual threads. Hence a GO application commonly has thousands of goroutines running concurrently.

  • Goroutines are managed by GO runtimes
  • go exroutine(a,b) starts a new goroutine that runs exroutine(a,b)
  • Here, the evaluation of exroutine,a,b happens in the current goroutine. The execution happens in the new goroutine.
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 10; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("is fun")
	say("learning go")
}

Channels:

  • Channels facilitate in sending and receiving values using the channel operator '<-'
  • Sending a to channel ch: ch <- a
  • Receive from ch and assing to a: a := <-ch (Data flows in the direction of the arrow)
  • Channels must be created before using it: ch := make(chan int)
  • Sends and receives block until the other side is ready and hence is synchronized.
package main

import "fmt"

func sum(s []int, c chan int){
   sum := 0
   for _,v := range s{
      sum += v
   }
   c <- sum
}

func main(){
   lst := []int{200, 100, 900, 30, 40, 70, 400, 450}
   c := make(chan int)
   go sum(lst[:len(lst)/2],c)
   go sum(lst[len(lst)/2:],c)
   x,y := <-c, <-c
   fmt.Println(x,y,x+y)
}
  • Channels can also be bufferred. The buffer length should be provided as the second argument to make: ch := make(chan int, 100)

  • A channel can be closed to indicate that no more values can be sent. close(ch)

  • Only the sender should close a channel (not the receiver).

  • Receivers can check if a channel has been closed using the second parameter a, ok := <-ch ok is False if the channel is closed

  • range can be used to keep receiving values until the channel is closed.

for i := range ch {
   fmt.Println(i)
} 
  • Closing a channel is not mandatory. It is only required to send a message to the receiver that no more values are being sent through the channel. It can also be useful to terminate a range loop.

Select

Select lets a goroutine wait on multiple communication operations. It chooses the case that is ready. If multiple are ready, it randomly makes a choice. Default case is run if no other case is ready

select {
	case c <- x:
		x, y = y, x+y
	case <-quit:
		fmt.Println("quit")
		return
	default:
         	fmt.Println("    .")
	}

Mutex:

Mutex or mutual exclusion ensures that only one goroutine can access a variable at a given time to avoid conflicts. Mutex is provided by the sync library which has two methods: Lock and Unlock A block of code can be executed in mutual exclusion by enclosing it in a call to lock Defer ensures the mutex is unlocked when required.

package main

import (
	"fmt"
	"sync"
	"time"
)

type mutexUsage struct {
	v   map[string]int
	mx sync.Mutex
}

func (m *mutexUsage) Incr(key string) {
	m.mx.Lock()
	m.v[key]++
	m.mx.Unlock()
}

func (m *mutexUsage) Value(key string) int {
	m.mx.Lock()
	defer m.mx.Unlock()
	return m.v[key]
}

func main() {
	m := mutexUsage{v: make(map[string]int)}
	for i := 0; i < 100; i++ {
		go m.Incr("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(m.Value("somekey"))
}

Previous Post: Methods and Interfaces

The All-purpose For loop

GO has a single looping construct which is the For loop. For loop has three components similar to that in C/Javascript:

  • Init statement - executed before the loop commences
  • Conditional statement - checked before every iteration
  • Post statement - executed after every iteration However, unlike C/Java/Javascript there is no paranthesis surrounding the components.
for i:=0; i< 10; i++ {
   fmt.Println(i)
}

For loop can also be written without the init and post statements:

for ;i<10; {
   fmt.Println(i)
}

For can also be a while loop:

for i<10 {
   fmt.Println(i)
}

Conditional Statement:

If statement does not have to be surrounded by a parantheses. However braces { } are required.

if x<y {
  fmt.Println(x+2)
}

If statement can also have an initial short statement that is executed before the condition. Variable declared inside the an if short statement is also available in the else clause

if x := math.Pow(x, n); x < y {
  x = x+2)
} else { 
  x = x - 2
}

Switch

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	fmt.Println(today + 1)
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
}
  • Switch in GO does not require a 'break' statement in the end of each case. It implicitely runs only on the selected case and not on all cases that follow.

  • The cases in GO need not be confined to integers only.

  • Switch cases evaluate from top to bottom and stops when it matches a statement.

  • Switch can also be written without a condition. This evaluates to switch true 

Defer

Defer statement defers the execution of a function until all the surrounding functions return. The defer statement is evaluated immediately but the function call is not executed until the surrounding functions return.

func main() {
 defer fmt.Println("lang")
 defer fmt.Println("GO")
 fmt.Println("Welcome")
 fmt.Println("to")
}

Deferred function calls are evaluated and pushed into a stack. The deferred function calls are executed in last-in-first-out order

Go or Golang is known for its Effienciency, Readability and Scalability to large systems. Some of its distinctive features are:

  • Clean, readable and modular code with minimum boilerplate.
  • Statically typed, fast compilation time.
  • Remote Package Management
  • goroutines (light-weight processes), channels and select statement
  • Networking and Multiprocessing.

Syntax:

Let us start with understanding the basic structure and syntax of the language:

package main

import (
  "fmt"
  "time"
)

func main() {
  fmt.Println("Welcome to Golang! The time is: ", time.Now())
}
  • "main" is the package from where the execution starts
  • Import: This programs uses the packages fmt and time. Multiple packages can be grouped into a single import statement (within the parantheses).
  • Exported names from a package should always start with a capital letter (ex. time.Now())

Compile and run on local:

  • Create a file welcome.go in the project directory. Ensure the PATH variable contains the go installation directory ("/usr/local/go/bin")
  • go build (creates an executable)
  • ./welcome

Functions:

Function can take zero or more arguments.

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

Notice that the type is written after the variable name. The type of the returned result is specified after the paranthesis.

When two or more function parameters share the same type, it can be written as: (x int, y int) can be written as (x,y int)

When the function returns more than one result, it is written as:

func sum(x,y int) (string,int) {
   return "sum: ", x+y
}

Naked return statement:

func sampleret(x,y int) (a,b int) {
   a = x + 10/3
   b = y + 10/3
   return
}

Returns the named return values. Should be used in short functions.

Variables:

'var' statement declares a list of variables. They can be at the Package level or function level.

Different ways of declaring variables:

var i int
var x,y int = 10, 11
a := 3 

(short assignment statement - can be used in the place of var inside the function. It is not available outside the function)

Basic types:

  • List of basic types:
bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte 

rune (represents a Unicode code point)

float32 float64

complex64 complex128
  • Representing Zero value for various types:
0 -> for int
"" (empty string) -> for strings
false -> for boolean
  • Type Conversion:
var i int = 100
var j float64 = float64(i)
  • Type Inference: When a variable is declared without specifying its type explicitely, it either takes the same type as the value on the right hand side:
var i int = 20
j := i (j is int)

or assigns a type depending on the value: i := 1.1234 (float64)

  • Constants: const Pi = 3.14 const cannot be declared using :=

Methods

GO has no Classes. However, it has Methods that can be defined on 'types'. A Method is a function with a special 'receiver' argument. In the example below, 'v' is the receiver of the type 'Square', to the method 'Sqroot'.

package main

import (
   "math"
   "fmt"
)

type Square struct {
   x, y float64
}

func (v Square) Sqroot() float64 {
   return math.Sqrt(v.x*v.x + v.y*v.y)
}

func main() {
   s := Square{3,4}
   fmt.Println(s.Sqroot())
}
  • Methods can be declared on non-struct types too.
  • A Method can only be declared on a type which is present in the same package as the method.

Pointer Receivers

A method can also be declared with the receiver being a Pointer. A Pointer receiver is very commonly used as it can modify the underlying value of the type. There can be two reasons to choose a pointer receiver instead of a value. First is to modify the underlying value and second to avoid copying the value on each method call. If the receiver is large, pointer receiver can prove to be more efficient.

package main

import (
   "math"
   "fmt"
)

type Square struct {
   x, y float64
}

func (v Square) Sqroot() float64 {
   return math.Sqrt(v.x*v.x + v.y*v.y)
}

func (v *Square) Scale(val float64) {
   v.x = v.x * val
   v.y = v.y * val
}

func main() {
   s := Square{3,4}
   s.Scale(100)
   fmt.Println(s.Sqroot())
}

Interfaces

Interface is a set of method signatures.

type ExInt interface {
	Ifs() float64
}
func(f float64) Ifs() float64 {
   return float64(-f)
}
  • Interfaces are implemented implicitly. No "implements" keyword required.
  • Interfaces can have 'nil' underlying value.
  • An empty interface can hold value of any type interface{}

Errors:

GO uses error values to express error states. An Error type is a built in Interface which looks like:

type error interface {
    Error() string
}

Functions return an Error value. The calling code should handle errors by testing if the error equals 'nil'

Reader:

  • The reader interface is a part of the 'io' package.
  • It identifies when end of stream of data is reached.
  • The Read method populates the number of bytes of data as specified.
  • The return value is 1) number of bytes populated and 2) an error value.
package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("You are using Readers!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
	fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
	fmt.Printf("b[:n] = %q\n", b[:n])
	if err == io.EOF {
	    break
 	    }
        }
}

Output:

n = 8 err = <nil> b = [89 111 117 32 97 114 101 32]
b[:n] = "You are "
n = 8 err = <nil> b = [117 115 105 110 103 32 82 101]
b[:n] = "using Re"
n = 6 err = <nil> b = [97 100 101 114 115 33 82 101]
b[:n] = "aders!"
n = 0 err = EOF b = [97 100 101 114 115 33 82 101]
b[:n] = ""

Next post

Pointers

  • GO has pointers as in C. A pointer holds the memory address of a value
  • & operator generates a pointer to its operand
  • '*' operator refers to the underlying value of the pointer
package main

import "fmt"

func main() {
  i := 24
  p := &i
  fmt.Println(p)
  *p = *p/2      // This is called "dereferencing" or "indirecting".
  fmt.Println(i)
}

Output:

0xc420082010
12

Structs

Structs is a collection of fields. The fields can be accessed using a dot(.)

package main

import "fmt"

type Emp struct {
   Name string
   Age int
   ID string
}

func main() {
  e1 := Emp{"John",23,"1234"}
  fmt.Println(e1)
  e1.Age = 30
  fmt.Println(e1)
}

Pointers to Structs

Stucts can be accesse through a struct pointer. The struct field(Name) of a struct pointer(sp) can be accessed as sp.Name

package main

import "fmt"

type Emp struct {
   Name string
   Age int
   ID string
}

func main() {
  e1 := Emp{"John",23,"1234"}
  sp := &e1
  fmt.Println(sp.Name)
  sp.Name = "James"
  fmt.Println(sp.Name)
}

Struct Literals

Struct Literal is a newly allocated struct value. It is not required to allocate values to all the fields. Just a few fields can be used. The order of assignment also doesn't matter. The unassigned values are implicitely assigned the Zero value of its type.

package main

import "fmt"

type strLit struct {
   X int
   Y string
   Z bool
}

var (
  s1 = strLit{}
  s2 = strLit{X:1,Y:"golang"}
)

func main() {
  fmt.Println(s1)
  fmt.Println(s2)
}

Arrays:

Array is of Fixed size in GO. It is declared as [n]T where is an Array of n values with the type T. Here are some ways of

func main() {
   var a [2]string
   a[0] = "Dobby is a"
   a[1] = "free elf"
   fmt.Println(a)
   fmt.Println(a[0],a[1])
   
   vowels := [6]string{"a","e","i","o","u"}
   fmt.Println(vowels)
}

Slice literal

A slice is a dynamicalled sized Array.

  • A slice does not store any data. Slices are like references to an Array. Modifying an element in the slice results in modifying the underlying array.
  • A slice literal creates an array and builds a slice that references it.
func main() {
   vowels := [6]string{"a","e","i","o","u"}
   var sub []string = vowels[:3]
	 fmt.Println(sub)
   
   t := []int{1,2,3,4}
	 fmt.Println(t)
}
  • When slicing, 0 is taken as the default low bound and the length of the slice as the default high bound (similar to Python Arrays). Ex. For the array: t [10]int these expressions are equivalent: t[:], t[:10], t[0:10], t[0:]

  • Slice Length: Number of elements it contains. Can be obtained using the function len(t)

  • Slice capacity: Maximum number of elements the slice can contain. Can be obtained using the function cap(t)

  • Nil Slice: The slice's len() and cap() is zero. The 'zero' value of a slice is 'Nil'

func main() {
  var s[]int 
  fmt.Println(s, len(s), cap(s))
    if s == nil {
      fmt.Println("nil!")
    }
}

Make - for creating slices

Dynamically sized arrays can be created using the 'make' function. The length and capacity of the slice can be passed as arguments to the 'make' function.

package main

import "fmt"

func main() {
  a := make([]int,4,6)
  printSlice("a",a)
	
  b := make([]int,2,6)
  printSlice("b",b)
	
  c:= b[:4]
  printSlice("c",c)
	
  d:= b[1:]
  printSlice("d",d)	
}

func printSlice(s string, x []int) {
  fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

Slices of slices:

Slices can contain other slices too.

package main

import "fmt"

func main() {
	sliceEx := [][]string{
	[]string{"Superman","Batman","Wonder Woman"},
	[]string{"Hulk","Ironman","Spiderman","Captain America"},
	[]string{""},
	}
	fmt.Println(sliceEx)
}

Appending to a slice:

New elements can be appended to a slice using the 'append' function. More than one elements can be appended in a single function call. The first paramenter is the slice followed by elements.

func main() {
 var t []int
 
 t = append(t,0)
 t = append(t,1,2,3,4)
}

Range

Range iterates over a slice. Range returns two values while iterating over a slice. First is the index of the element and second a copy of the element at that index.

package main

import "fmt"

var t = []int{1, 3, 6, 9, 12, 15, 18, 21}

func main() {
	
	for i,v := range t {
	   fmt.Printf("%d => %d\n", i, v)
	}
}
  • Index or Value can be skipped by assigning it to _.
  • If only indexes are required, the "value" can be dropped entirely.
func main() {
	
	fmt.Printf("Values:\n")
	for _,v := range t {
	   fmt.Printf("%d, ", v)
	}
	
    fmt.Printf("\nIndex:\n")
    for i:= range t {
	   fmt.Printf("%d, ", i)
	}

}

Map

Map is nothing but a key-value pair. 'Make' function can be used to initialize a map Zero value of a Map is 'Nil'. No keys can be added to a 'nil' map

package main

import "fmt"

var Emp map[string]int

func main() {
   Emp = make(map[string]int)
   Emp["John"] = 34
   Emp["Jeff"] = 20
   fmt.Println(Emp)
   fmt.Println(Emp["John"])
}

Map Literals:

package main

import "fmt"

type address struct {
  street, city string
  }

var Emp = map[string]address{
	"John": address{"abc street","London"},
	"Jeff": {"def street","Boston"},
}

The top-level type name (address in this case) can be omitted as well.

Mutating Maps

m = make(map[string]int)

  • Insert/Update" m["Mary"] = 58

  • Retrieve an element: fmt.Println(m["Mary"])

  • delete: delete(m,"Mary")

  • Check if a key is present: val, present := m["Jerry"] If the key is present, val will return the value and present will be True. If the key is not present, val will be 0 and present will be False.

Implementing Word Count

A small exercise to create a map which will contain the count of each letter in a given word:

package main

import (
	"strings"
	"fmt"
)

func WordCount(s string) map[string]int {
    split := strings.Fields(s)
	wc := make(map[string]int)
	for i:=0;i<len(split);i++ {
	   wc[split[i]] = len(split[i])
	}
	return wc
}

func main() {
	fmt.Println(WordCount("I am learning Go!"))
}

Function values

In GO, Functions can also be passed around similar to values.

package main

import (
  "fmt"
  "math"
)

func compute(fn func(float64, float64) float64) float64 {
   return fn(4,3)
}

func main() {
   sqrt := func(x,y float64) float64 {
       return math.Sqrt(x*x + y*y)
    }
   fmt.Println(compute(math.Pow))
   fmt.Println(compute(sqrt))
}

Function Closures

Closure is a function that references values from outside its body.

package main

import (
   "fmt"
)

func closure() func(int) int {
    sum := 0
    return func(x int) int {
        sum +=x
        return sum
    }
}


func main() {
    sum1,sum2 := closure(), closure()
    for i:=0;i<10;i++ {
        fmt.Println(sum1(i),sum2(-1*i))
    }
}

Previous Post:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.