Tuesday, 19 February 2019

Functions, Methods, and Interfaces in Go - Notes on Coursera course

Week 1 - FUNCTIONS AND ORGANIZATION


Why Use Functions?


  • function - set of instructions grouped together, usually with a name
  • all programs in Go have to have function main() - where program's execution begins
func main() {
   fmt.Printf("Hello, world!")
}
  • main() function is a special in a sense we don't call it explicitly which is not the case for other functions
func PrintHello() {
   fmt.Printf("Hello, world!")
}

func main() {
   PrintHello()
}
  • function declaration starts with keyword func, function name, arguments, return type and body
  • Why using functions?
    • reusability (within the same project or across multiple projects via libraries)
    • abstraction
      • hiding details of implementation; only input-output behaviour is what we need to know (we look at function as a black box)
      • improves understandability
        • naming 
        • grouping of function calls


Function Parameters and Return Values

  • functions need some data to work on - they can be passed via function parameters
  • parameters are listed in parentheses after function name
  • arguments are supplied in the call
func foo(x int, y int) {
   return x * y
}

func main() {
   foo(2, 3)
}
  • Parameter Options
    • if no parameters are needed, put nothing in parentheses; you still need parentheses
    • list arguments of same type
func foo(x, y int) {
}
  • Return Values
    •  Functions can return value as result
    • type of return value after parameters in declaration
    • function call is on the right side of an assignment
func foo(x int) int {
   return x + 1

y := foo(1) // y gets assigned value 2
  • Functions can have multiple return values
    • their types must be listed in the declaration
func foo2(x int) (int, int) {
   return x, x + 1
}

a, b := foo2(3) // a is assigned 3, b 4

Call by Value, Reference

  • how are arguments passed to parameters during the function call
  • Call by Value
    • arguments are copied to parameters
    • data used is the copy of the original;
    • called function can't interfere with the original variables in the calling function
    • modifying parameters has no effect outside the function
func foo(y int) {
   return y + 1
}

func main() {
   x := 2
   foo(x)
   fmt.Print(x) // still 2
}
  • tradeoffs of call by value
    • advantage: 
      • data encapsulation
      • function variables are changed only inside the function
    • disadvantage:
      • copying time
      • large object may take a long time to copy
  • Call by Reference
    •  programmer can pass a pointer as an argument
    • called function has direct access to caller variable in memory
    • we don't pass data but a pointer to data (the address of data)
func foo(y *int) {
   *y = *y + 1
}

func main() {
   x := 2
   foo(&x) // foo gets a copy of the address where x is in memory; foo is modifying x
   fmt.Print(x) // 3
}
  • tradeoffs of Call by Reference
    • advantages:
      • copying times; only the address is copied, not data
    • disadvantage:
      • (no) data encapsulation
      • called function can change data/variables in the calling function

Passing Arrays and Slices


  • how to pass an array to a function?
  • array arguments are copied
    • array can be big so this can be a problem
func foo(x [3]int) int { // NOTE size of array => argument is of type array (not slice)
   return x[0]
}

func main() {
   a := [3]int{1, 2, 3}
   fmt.Print(foo(a)) // entire array is copied!
}
  • instead, we can pass a reference - a pointer to the array (array pointer)
    • this can be messy for having to reference and dereference
func foo(x *[3]int) {
   (*x)[0] = (*x)[0] + 1
}

func main() {
   a := [3]int{1, 2, 3}
   foo(&a)
   fmt.Print(a) // [2 2 3]
}
  • proper Go approach is to pass slices instead of arrays
  • in Go: get used to use slices instead of arrays! you can almost always use slices instead of arrays
  • slice is a structure that contains 3 things:
    • pointer to an underlying array
    • length
    • capacity
  • if we pass a slice, we actually pass a copy of the pointer to an array
    • function can use that pointer directly, without the need to dereference/reference explicitly
func foo(sli int) int {
   sli[0] = sli[0] + 1
}

func main() {
   a := []int {1, 2, 3} // NOTE no size is specified between [] => this is a SLICE declaration!!!
   foo(a)
   fmt.Print(a) [2 2 3]
}
  • instead of arrays or pointers to arrays pass slices to functions!!!

Well-Written Functions

  • how you should write functions so your code is well-organized and understandable
  • Understandability
    • code = functions + data
    • if you are asked to find a feature, you should be able find it quickly; also, your peer reviewers should also be able to find it quickly
    • if you are asked where data is used (defined, accessed, modified), you should be able to find it quickly
  • Debugging Principles
    • e.g. code crashes inside a function
    • two options for a cause:
      • function is written incorrectly
        • e.g. sorts a slice in a wrong order
      • data that function uses is incorrect
        • e.g. sorts slice correctly but slice has wrong elements in it
  • Supporting Debugging
    • functions need to be understandable
      • determine if actual bahaviour matches desired behaviour - this shall be easy to do
    • data needs to be traceable
      • we should be able to trace where did data come from
      • global variables complicate this
        • anybody can write into them
        • otherwise we know that calling function passed data...

Guidelines for Functions

  • Function Naming
    • behaviour should be understandable at a glance
    • parameter naming counts too
func ProcessArray(a []int) float {}  // BAD! What kind or processing? What is the meaning of a?

func ComputeRMS(samples []float) float {} // GOOD. We know what f-n does, and what is the argument

  • Functional Cohesion
    • function should perform only one "operation"
    • an "operation" depends on context
    • even from a function name we can see that it does only one thing
      • if you need to put two or more actions in a function name, that should raise an alert
    • merging behaviours makes code complicated
  • Reduce number of parameters
    • use few parameters
    • debugging requires tracing function input data
      • it's more difficult with large number of parameters
    • if you function has many parameters it may have bad functional cohesion - it does too many things; each functionality ("operation") needs its own set of inputs; 
    • group related arguments into a structure
      • e.g. if we want to pass 3 points in space to a function we might need to pass 2 x 3 = 9 arguments
      • improvement: define a struct point and pass 3 points: 
type point struct {x, y, z float}
  • best: define a struct triangle and pass a triangle as a single argument:
type triangle struct {p1, p2, p3 point}


Function Guidelines

  • Function Complexity
    • function length is the most obvious measure
    • functions should not be complex; easier to debug
    • short functions can be complex too
  • Function length
    • write complicated code with simple functions
    • function call hierarchy
  • Control-flow Complexity
    • how many control-flow paths are there in a function?
    • paths from the start to the end of function
    • if there are no if statements: one control-flow path
    • if there is if statement: two control-flow paths
    • control-flow describes conditional paths
    • functional hierarchy can reduce control-flow complexity: separate conditional code into separate functions

Week 2 - FUNCTION TYPES

First-Class Values

  • Go treats functions as a first-class values
  • functions are First-class
  • Go implements some features of functional programming
  • Go treats functions as any other type like int, float...
    • variables can be declared to be a function type and then assigned a function
    • functions can be created dynamically, on the fly
      • so far we've been creating them dynamically, in the global space we'd use func
      • but they can be created dynamically, inside other functions
    • functions can be passed as arguments to functions
    • functions can be returned from functions
    • functions can be stored in structs

Variables as Functions 

  • declare variable as function
    • variable becomes an alias (another name) for that function
var funcVar func(int) int // "func(int) int" is function signature

func incFn(x int) int {
   return x + 1
}

func main() {
   funcVar = incFn // in assignment just use function name, without () as we're not calling function here
   fmt.Print(funcVar(1))
}

Functions as Arguments

  • functions can be passed to other functions as arguments
    • we have to use keyword func
func applyIt(afunc func(int) int, val int) int {
   return afunc(val)
}


func incFn(x int) int { return x + 1 }
func decFn(x int) int { return x - 1 }

func main() {
   fmt.Println(applyIt(incFn, 2)) // 3
   fmt.Println(applyIt(decFn, 2)) // 1
}

Anonymous Functions

  • functions don't need to have names
  • functions with no name are called anonymous 
  • when passing function to another function you usually don't need to name passed function
    • function is created right there at the call 
    • this comes from lambda calculus
func main() {
   v := applyIt(func (x int) int { return x + 1}, 2);
   fmt.Println(v) // 3
}

Returning Functions

Functions as Return Values

  • functions can create functions and return them
    • new function can have a different set of parameters; controllable parameters
  •  example: Distance to Origin function
    • takes a point (x, y coordinates)
    • returns distance to origin
    • what if I want to change the origin?
      • option1: origin becomes a parameter
      • option 2: we create a function for each origin (o_x, o_y)
        • origin is build in the returned function
        • func (float64, float64) float64 is the type of the function returned
func makeDistOrigin(o_x, o_y float64) func (float64, float64) float64 {
   fn := func (x, y float64) float64 {
      return math.Sqrt(math.Pow(o_x, 2) + math.Pow(o_y, 2))
  }
  return fn
}

Special-Purpose Functions

  • we make special-purpose functions by giving them parameters (e.g. Dist1 and Dist2 have different origins)

func main() {
   Dist1 := MakeDistOrigin(0, 0)
   Dist2 := MakeDistOrigin(2, 2)
   fmt.Println(Dist1(2, 2))
   fmt.Println(Dist2(2, 2))
}

Environment (Scope) of a Function

  • every function has an environment ("scope")
  • set of all names that are valid inside a function; that you can refer inside the function
  • environment includes names defined locally, in the function
  • Lexical Scoping: Go is lexically scoped 
    • environment includes names defined in block where the function is defined
    • BK: this is called variable capturing 
    • when you start passing around functions as arguments, the environment goes along with functions 
var x int
func foo(y int) {
   z := 1
   ...
}

Closure

  • function + its environment, together
  • in Go, it is implemented as a structure which contains pointer to function and pointer to environment
  • when you pass function as an argument to another function, you pass its environment with it
  • at the place where this function is executed, it still has an access to variables from the place where it was defined
  • e.g. o_x and o_y are carried with returned function, and are accessible when its called later, wherever and whenever is called
  • variables are coming from the closure, from the environment where function was defined
func makeDistOrigin(o_x, o_y float64) func (float64, float64) float64 {
   fn := func (x, y float64) float64 {
      return math.Sqrt(math.Pow(o_x, 2) + math.Pow(o_y, 2))
   }
   return fn
}


Variadic and Deferred

Variable Argument Number

  • it is possible to pass variable number or arguments to function; such function is called variadic
  • to specify this use ellipsis character: ...
  • such argument is treated as a slice inside the function
func getMax(vals ...int) int {
   maxV := -1
   for _, v := range vals {
      if v > maxV {
         maxV = v 
      }
   }
   return maxV


  • How to pass list of arguments to variadic function?
    • you can pass a comma-separated list of arguments
    • you can pass a slice
      • need a ... suffix
func main() {
   fmt.Println(getMax(1, 2, 6, 4))
   vslice := []int {1, 3, 6, 4}
   fmt.Println(getMax(vslice...))
}

Deferred Function Calls


  • call can be deferred until surrounding function completes
  • they don't get executed where they are explicitly called but after the surrounding function is done
  • typically used for cleanup activities
  • use keyword defer
func main() {
   defer fmt.Println("Bye!") // "Bye!" printed after "Hello!"
   fmt.Println("Hello!")
}
  • the arguments are NOT evaluated in a deferred way, they are evaluated immediately but the call is deferred
  • if you pass an argument, it is evaluated right there where defer statement is 
func main() {
   i := 1
   defer fmt.Println(i + 1) // 2 is printed second time
   i++ // 2
   fmt.Println(i) // 2 is printed first time
}


Week 3 - OBJECT ORIENTATION IN GO

Classes and Encapsulation

Classes 

  • What is OOP?
  • Go supports OOP
  • It does not have classes but something equivalent (structs)
  • What is class? Collection of data fields and functions that share a well-defined responsibility (they are all related to the same concept)
    • function in a class is called a method
  • Example: Point class
    • used in geometry program
    • data: x and y coordinate
    • functions:
      • DistToOrigin(), Quadrant()
      • AddXOffset(), AddYOffset()
      • SetX(), SetY()
  • class is a template; contains fields, not data

Object

  • instance of the class
  • contains data
  • Example: instances of Point class

Encapsulation

  • associated with OOP (and generally, with abstraction)
  • if there is a program using your class, you want to hide details
  • you want to prevent someone changing internal data; therefore we provide public methods that shall be used to modify the state of the object from the outside
  • Example: double distance to origin (double x and y)
    • option 1 (safe): expose method DoubleDist() which doubles x and y internally
    • option 2 (not safe): allow programmer to access x and y directly; but programmer can make mistake if for example they double x but forget to double y
    • by exposing methods we prevent such mistakes and object will always be in good state

Support for Classes (1)

No "class" keyword


  • there is no "class" keyword in Go
  • most OO languages have class keyword
  • Data fields and methods are defined inside a class block
  • example in Python:

class Point:
   def _init_(self, xval, yval):
      self.x = xval
      self.y = yval


Associating Methods with Data

  • Go has different way of associating methods with data
  • Go is using "receiver types"
  • data is some type 
  • method has a receiver type that it is associated with
  • BK: the approach is the same as in C: we have some struct type and if some function has to deal with it, we just pass to it a pointer to that struct. The terminology reminds me of Objective-C
  • type and function have to be defined in the same package
  • when we call a method we use dot notation
  • example: we want to associate function Double with our custom type MyInt
    • MyInt is the receiver type - it is specified before the name of the function
    • mi is the receiver object (instance of the receiver type) that double would be called on
type MyInt int

func (mi MyInt) Double () int { 
   return int(mi *2) 
}

func main() {
   v := MyInt(3)
   fmt.Println(v.Double())
}

  • Double() could be defined for multiple types (to have multiple receiver types); Go looks what's the type left of the dot (.) operator to find out what is the receiver type (and so which Double() function to call)
  • in the example above mi becomes an implicit argument of function Double() (just like this - pointer to the current instance of the class - is an implicit argument in C++ classes' methods)

Implicit Method Arguments

  • although it seems that Double() takes no arguments, there is one implicit (hidden) argument: and instance (object) of the receiver type
  • object v is an implicit argument to the method
    • call by value (that's how argument passing is done in Go)
    • a copy of v is made and passed to the function

Support for Classes (2)

  • in a normal OOP language lots of different data (fields in class) is associated with any number of methods
  • the same can be done in Go: we'll just make a struct with lots of various data and make it to be a receiver type
  • in struct you can group together arbitrary number of arbitrary data 

Structs, again

  • struct types compose data fields 
    • this is traditional feature of classes
type Point struct {
   x float64
   y float64
}

Structs with Methods

  • structs and methods together allow arbitrary data and functions to be composed
func (p Point) DistToOrig() {
   t := math.Pow(p.x, 2) + math.Pow(p.y, 2)
   return math.Sqrt(t)
}

func main() {
   p1 := Point(3, 4)
   fmt.Println(p1.DistToOrig())
}

Encapsulation

Controlling Access

  • Go provides lots of different support of encapsulation and how to keep data private
  • we want to be able to control data access
    • we want people to use data in a way we define - via functions/methods
    • we can define a set of public functions that allow another/external package to access the data
package data

var x int = 1

func PrintX() {
   fmt.Println(x)
}
-------------------
package main

import "data"

func main() {
   data.PrintX()
}
  • PrintX function starts with capital leter => it gets exported
  • package main can access (see) x only through that exported function 
  • x can't be modified externally in the example above but we can allow that if we export another method which allows that

Controlling Access to Structs

  • we can do something similar to struct members
  • hide fields of structs by starting field name with lower-case letter
  • define public methods which access hidden data
  • example: need InitMe() to assign hidden data fields
package data

type Point struct {
   x float64
   y float64
}

func (p *Point) InitMe(xn, yn float64) {
   p.x = xn
   p.y = yn
}

func (p *Point) Scale (v float64) {
   p.x = p.x * v
   p.y = p.y * v
}

func (p *Point) PrintMe() {
   fmt.Println(p.x, p.y)
}

-----------------------------
package main
import "data"

func main() {
   var p data.Point
   p.InitMe(3, 4)
   p.Scale(2)
   p.PrintMe()
}

  • access to hidden fields only through public methods

Point Receivers

Limitations of Methods

  • there are some limitations to the process of associating methods with receiver types we described above
(1)  Method cannot change the state of receiver object as it's passed by value 
  • receiver type/object is implicitly passed to the method
  • it is passed by value (like any function argument in Go) => method receives only its copy => method can't change receiver object (method can't modify  the data inside the receiver)
  • example: OffsetX() should increase coordinate x in object p1
func main() {
   p1 := Point(3, 4)
   p1.OffsetX(5)  // only temp copy of p1 inside OffsetX() is changed
}
(2) Large Receivers

  • if receiver object is large, lots of copying is required when you make a call
  • all object is copied onto the stack
type image [100][100]int
func main(){
   i1 := GrabImage()
   i1.BlurImage() // 10000 bytes gets copied on stack - this can be slow
}

Solution:

Pointer Receivers

  • instead of passing objects by value we can pass by reference (pointer)
  • instead of using regular types for receiver types, we can use pointer to those types as receiver type
func (p *Point) OffsetX(v float64) {
   p.x = p.x + v
}

Point Receivers, Referencing, Dereferencing

No Need to Dereference

  • when using a pointer receiver there is no need to perform explicit dereferencing (as in the previous example where Point is referenced as p, not *p)
  • dereferencing is automatic with . operator

No Need to Reference


func main() {
   p := Point(3, 4)
   p.OffsetX(5)  // no need to do something like (&p).OffsetX()
   fmt.Println(p.x)
}

Using Pointer Receivers

  • Good programming practice
    • all methods for a type have pointer receivers, or 
    • all methods for a type have non-pointer receivers
  • This is for mixing pointer/non-pointer receivers for a type will get confusing
  • pointer receiver allows modification

Week 4 - INTERFACES FOR ABSTRACTION

Polymorphism

  • one of OOP properties
  • ability of an object to have different "forms" depending on the context
  • example: Area() function - the function with the same name can do the same thing but in a different way, depending on the context
    • rectangle: area = base * height
    • triangle: area = 0.5 * base * height
  • these two functions
    • at high level of abstraction, they are identical in a way what they do; they do the same thing - compute the area
    • at low level of abstraction, they are different, in a way how do they compute the area
  • We need Go's support for polymorphism to achieve this
  • How is polymorphism implemented in traditional OOP languages?

Inheritance

  • Go does NOT have inheritance
  • there are parent-child (base-derived; superclass-subsclass) relations between classes
    • superclass is a top level class
    • subclass extends from superclass, subclass inherits data and methods from a superclass
    • Example: 
      • Speaker superclass - represents anything that can make noise/speak
        • Speak() method prints "<noise>"
      • Subclasses Cat and Dog
        • Also have the Speak() method, inherited from the Speaker superclass
        • Cat and Dog are different forms of Speaker

Overriding

  • subclass redefines a method inherited from the superclass 
  • example: Speaker, Cat, Dog
    • Speaker Speak() prints "<noise>"
    • Cat Speak() prints "meow"
    • Dog Speak() prints "woof"
  • without overriding Cat and Dog Speak() methods would print "<noise>" but overriding allows them to redefine Speak() methods to print what they want
  • Speak() is polymorphic: in context of Cat it prints "meow" and in context of Dog, it prints "woof"
  • although overriden in subclasses, the method keeps the same signature

Interfaces

  • interface is a concept used in Go to help us get polymorphism
  • we don't need inheritance or overriding to get polymorphism, we can get it with interfaces
  • interface is a set of method signatures (name, parameters, return values)
    • implementation is not defined
  • it is used to express conceptual similarity between types
  • example: Shape2D interface has two methods: Area() and Perimeter()
    • all 2D shapes must have Area() and Perimeter()
    • any type that has these two methods can be considered to be 2D shape

Satisfying the Interface

  • types satisfies the interface if type defines all methods specified in the interface
    • same method signatures (names, args, return values)
    • example: Rectangle and Triangle types satisfy the Shape2D interface if:
      • have Area() and Perimeter() methods
      • Additional methods are OK
  • similar to inheritance with overriding

Defining an Interface Type

  • use keyword interface

type Shape2D interface {
   Area() float64
   Perimeter() float64

type Triangle {
   ...
}

func (t Triangle) Area() float 64 {
   ...
}

func (t Triangle) Perimeter() float 64 {
   ...
}

  • in the example above, we can say that Triangle implements (satisfies) interface Shape2D
  • we don't state explicitly that Triangle implements interface; we just present to compiler the interface and methods and it infers which types can are satisfying which interfaces
  • we don't care what data is in Triangle, which fields/properties it has; it only matters that that type is set as receiver type for functions with the same signature as defined in the interface

Interface vs. Concrete Types

Concrete vs Interface Types

  • concrete and interface types are fundamentally different
  • Concrete Type
    • a regular type
    • Specifies the exact representation of the data and methods 
    • fully specified
    • complete method implementation included
    • it has data which is associated with it
  • Interface type:
    • just specifies some method signatures
    • not data
    • implementations are abstracted
    • interface type eventually gets mapped to a concrete type

Interface Values

  • can be treated like other values
    • assigned to variables
    • passed, returned
  • interface values have two components
    • Dynamic Type: concrete type which it is assigned to (like a class which implements an interface in classic OOP languages)
    • Dynamic Value: value of the dynamic type (like an instance of the class which implements an interface in classic OOP languages)
  • interface value is actually a pair (dynamic type, dynamic value)

Defining an Interface Type


type Speaker interface {
   Speak()
}

type Dog struct {
   name string
}

func (d Dog) Speak() {
   fmt.Println(d.name)
}

func main() {
   var s1 Speaker // interface value
   var d1 Dog{"Brian"} // 
   s1 = d1 // legal as Dog satisfies interface Speaker; 
   s1.Speak()
}
  • interface type is Speaker
  • interface value is s1
  • dynamic type is Dog
  • dynamic value is d1
  • pair is (Dog, d1)

Interface with Nil Dynamic Value

  • an interface can have a nil dynamic value (no dynamic value)
var s1 Speaker
var d1 *Dog // pointer
s1 = d1 // legal
  • d1 is pointer to Dog, it is not concrete object, has no data in it; d1 has no concrete value yet
  • s1 has dynamic type - Dog, but has NO dynamic value

Nil Dynamic Value

  • interface with dynamic type but not dynamic value; it is legal to call interface methods on nil dynamic value
  • can still call the Speak() method of s1
  • doesn't need a dynamic value to call interface methods
  • need to check inside the method
func(d *Dog) Speak() {
   if d == nil { // does it have dynamic value or not?
      fmt.Println("<noise>")
   } else {
      fmt.Println(d.name)
   }
}

var s1 Speaker
var d1 *Dog
s1 = d1
s1.Speak() // it is legal to call function on a non-assigned pointer!

Nil Interface Value

  • interface with nil dynamic type
  • very different from an interface with a nil dynamic value
  • we can't call interface methods as there is no underlying concrete type with methods to call, there is no method implementations
  • Nil dynamic value and valid dynamic type
    • can call a method since type is known
    • example:
var s1 Speaker
var d1 *Dog
s1 = d1
s1.Speak()
  • Nil dynamic type
    • cannot call a method, runtime error
var s1 Speaker
s1.Speak() // error!


Using Interfaces

  • why would we need interfaces? why are they used?

Ways to Use an Interface

  • need a function which takes multiple types of parameter
    • e.g. we want function to take either type int or type float
  • we want function foo() to accept parameter of type X or type Y
  • we can:
    • define interface Z
    • make types X and Y satisfy Z (BK: this is something like class extensions...as any type can at any time be extended to satisfy any interface; this can happen after some concrete type is defined)
    • make Z to be the type of the foo() argument
    • interface methods must be those needed (called) by foo()

 Pool in a Yard

  • I need to put a pool in my yard
  • Pool needs to fit in my yard
    • total area must be limited
  • Pool needs to be fenced
    • total perimeters must be limited
  • Need to determine if a pool shape satisfies criteria
  • FitInYard() bool
    • takes a shape as argument
    • returns true if the shape satisfies criteria

FitInYard()

  • many possible shape types
    • rectangle, triangle, circle, etc...
  • FitInYard() should take many shape types
  • Valid shape types must have:
    • Area()
    • Perimeter()
  • Any shape with these methods is OK

Interface for Shapes

type Shape2D interface {
   Area() float64
   Perimeter float64
}

type Triangle {...}
func (t Triangle) Area() float64 {...}
func (t Triangle) Perimeter() float64 {...}

type Rectangle {...}
func (r Rectangle) Area() float64 {...}
func (r Rectangle) Perimeter() float64 {...}

  • Rectangle and Triangle satisfy interface Shape2D
func FitInYard(s Shape2D) bool {
   if (s.Area() < 100 && s.Perimeter() < 100) {
      return true}
   else {
      return false
   }
}
  • parameter is any type that satisfies Shape2D interface

Empty Interface

  • specifies no methods
  • all types satisfy the empty interface
  • use it to have a function accept any type as a parameter
  • use interface{} to specify it
  • val can be any type
func PrintMe(val interface{}) {
   fmt.Println(val)
}

Type Assertions

Concealing Type Differences

  • a lot of point of interfaces is to hide differences (or highlight similarities) between types
    • example: Triangles and Rectangles are treated in the same way in FitInYard() - as long as they satisfy Shape2D interface
    • different types which has some similarities are treated in the same way
  • sometimes you need to treat different types in different ways
  • sometimes we need to differentiate based on the type, to figure out what is the concrete type
  • in FitInYard() it does not matter what is the concrete type; it can be Rectangle or Triangle

Exposing Type Differences

  • example: Graphics program
  • DrawShape() will draw any shape, it can take any Shape2D as an argument
    • func DrawShape(s Shape2D) { ...
  • Underlying API has different drawing functions for each shape so they have to take particular, specific, concrete types as arguments:
    • func DrawRect (r Rectangle) { ...
    • func DrawTriangle (t Triangle) { ...
  • Inside DrawShape() we need to find out what is the concrete type of s so we know which underlying function to call; concrete type must be determined
  • type assertions are used for that

Type Assertions for Disambiguation

  • type assertions can be used to determine and extract the underlying concrete type 

func DrawShape(s Shape2D) bool {
   rect, ok := s.(Rectangle)
   if ok {
      DrawRect(rect)
   }
   tri, ok := s.(Triangle)
   if ok {
      DrawTriangle(tri)
   }
}

  • type assertions extract Rectangle from Shape2D
    • concrete type has to be specified in parentheses
  • If interface contains specified concrete type 
    • rect == specified concrete type
    • ok == true
  • If interface does not contain concrete type 
    • rect == zero value for that type
    • ok == false

Type Switch 


  • Interface can be satisfied by many concrete types; we might be interested only in some of them
  • Another way to do this disambiguation is to use switch
  • switch statement used with a type assertion
    • use keyword type in parentheses: .(type)
    • if s is Triangle then sh will be Triangle
func DrawShape(s Shape2D) bool {
   switch := sh := s.(type) {
   case Rectangle:
      DrawRectangle(sh)
   case Triangle:
      DrawTriangle(sh)
   }
}


 Error Handling

  • common use of Error interface in Go

Error Interface

  • there are lot of Go functions built in packages where return two values: 
    • value
    • error (error interface objects which indicates an error)

type error interface {
   Error() string // prints the error message
}

  • correct / successful operation: error == nil
  • incorrect / failed operation: error != nil
  • if Go function returns an error (usually as a second value) you should check that error and handle it! (BK: compiler will complain if returned error is not checked explicitly in the code)
f, err := os.Open("harris/test.txt")
if err != nil {
   fmt.Println(err)
   return
}

  • check whether error is nil
  • if it's not nil, handle it

















































Thursday, 14 February 2019

Getting Started with Go - Notes on Coursera course

Here are my notes from the course "Getting Started with Go" on Coursera. This is the first course from "Programming with Google Go" Specialization.


Week 1 - Getting Started with Go


  • efficient as C
  • sharing
  • runs fast
  • garbage collection
  • simpler objects
  • essentially OOP; there is a concept of object
  • object is simplified => makes it faster/simple to use
  • concurrency is efficient
  • machine, assembly, high-level language
  • machine language is executed on CPU; very simple add, mul...
  • assembly - almost machine language but is human readable
  • compiled language with some features of interpreted languages, like Garbage Collection
  • weakly OOP
  • go does not use term “class” but “struct”s (containing data) with associated methods
  • no inheritance, no generics, no constructors
  • advantages of Go: its implementation of concurrency
  • Moore’s Law not applies anymore: number of transistors is not increasing over time for temperature/power constraints;
  • number of cores is increasing
  • Go recommends directory hierarchy: common organization is good for sharing;
  • src, pkg (libraries), bin
  • one workspace for many directories
  • GOPATH: workspace directory; defined during installation
  • package: group of related source files; can be imported by other packages;first line in source file names the package

file1.go:

package annpkg


file2.go:

package billpkg

file3.go:

import (
   “annpkg”
   “billpkg”
)

  • There must be one package called main; that’s where execution starts; compiled main package is an executable; other packages compile into packages
  • main package has to have function main

package main
import “fmt” // note: it’s NOT include!!!

func main() {
   fmt.Printf(“Hello, world\n”)
}

  • import - used to access other packages
  • Go standard library includes many packages, like fmt
  • Go searches packages in GOROOT and GOPATH directories first;
  • Go Tool: > go
  • go build - to compile the source code; when compiling main package.exe file has the same name as .go file
  • go doc - prints documentation for package
  • go format - formats source code files (e.g. indentation...)
  • go get - downloads and installs new packages
  • C:\dev\go\src>go get github.com/BojanKomazec/coursera-go-specialization
  • can't load package: package github.com/BojanKomazec/coursera-go-specialization: no Go files in C:\dev\go\src\github.com\BojanKomazec\coursera-go-specialization
  • (but it created a coursera-go-specialization directory regardless, which is good)
  • go list - installs all installed packages
  • go run - compiles and executes
  • go test - runs tests using files ending in “_test.go”
  • names
  • variables: have to have name and type

var x int
var x, y int

  • type defines what type of data and which operations can be performed on the variable
  • floating point
  • strings: unicode
  • to improve clarity/readability alias name for type (with type keyword):

type Celsius float64
type IDnum int

var temp Celsius
var pid IDnum

  • initializing variables
    • in the declaration
    • with explicit type specification
var x int = 100
    • with implicit type inference
var x = 100
    • after the declaration
var x int
x = 100

  • uninitialized variables have value of 0, empty string
  • short variable declaration (:=) declaration & initialization (type is inferred; can be done only inside a function):
x := 100


Week 2 - Basic Data Types


Pointers
  • & (ampersand) - gets address of variable or function
  • * (star operator) - returns data at the address; comes before pointer

var x int = 1
var y int
var ip *int
ip = &x
y = *ip


  • new() - another way to create a variable; it returns a pointer to a variable; variable is initialized to 0 by default

ptr := new(int)
*ptr = 3


  • variable scope - where variable can be accessed
  • in Go - variable scoping is done through blocks - sequence of declarations and statements within matching { }
  • blocks can be hierarchical
  • explicit blocks: when you write { } yourself; e.g. functions - each function has its own block
  • implicit blocks:
    • universe block - all Go source code
    • package block - all source in a package
    • file block - all source in a file (package can contain many files)
    • if, for, switch statements
    • clause in switch or select
  • Lexical scoping - this defines how variable references are resolved; Go is a lexically scoped language using blocks.
  • bi >= bj if bj is defined inside bi ; “defined inside” is transitive
  • variable is looked in the current/local block and then in the next bigger block
  • Variable is accessible from block bj if:
    • variable is declared in bi and
    • bi >= bj
  • variables have to be allocated and deallocated (when is not used anymore so memory space is made available) in memory; this has to be done in a timely fashion - before we run out of memory
  • stack vs heap
    • stack (traditionally, not in Go): dedicated to function calls; local variables for function are allocated in stack; they are deallocated automatically when function returns;
    • heap: persistent memory - you have to explicitly deallocate it; in C: malloc - to allocate; free - to deallocate it; error-prone (if you forget to call free) but fast;
  • Garbage Collection
  • you don’t want to deallocate memory prematurely
  • How to determine when a variable is no longer in use?

func foo() *int {
   x := 1
   return &x
}

func main() {
   var y *int
   y = foo()
   fmt.Printf(”%d”, *y)
}

  • GC is done by interpreter; GC keeps track of pointers/references to variables; only when all references are gone, it deallocates variable; GC keeps track of;
  • compiled languages like C and C++ can’t do this
  • Go is compiled language which has GC built in
  • Go compiler determines whether variable will go on stack or on heap; you don’t need to determine that
  • GC takes some time - that’s the tradeoff
  • comments: //
  • block comments: /* */
  • fmt.Printf():
    • conversion characters %d, %s
    • https://stackoverflow.com/questions/53961617/call-has-possible-formatting-directive
  • fmt.Prtintln()
    • does NOT support conversion/formatting characters
    • adds a new line character at the end
    • in the output, adds a SPACE character after each argument
      • it's not necessary to add a SPACE explicitly at the end of "a = ", "a =" will work ok:
fmt.Println("a =", a)
  • Integers
    • int
    • int8, int16, int32, int64 (2s complement: most significant bit is used for sign)
    • unsigned: uint8...etc
    • int vs int32 vs in64

  • Integers: binary operators
    • arithmetic
    • comparison
    • boolean
  • Type conversions
    • most binary operations (including assignment) require both operands to be of the same type
var x int32 = 1
var y int16 = 2
// [go] cannot use y (type int16) as type int32 in assignment
x = y

    • convert types with T() operation
x = int32(y)
  • Floating point
    • float32 ~6 digits of precision
    • float64 ~ 15 digits of precision
    • decimal representation: x float64 = 123.45
    • scientific representation: x float64 = 1.2345e2 (e2 = 10^2; base 10)
    • complex numbers: var z complex128 = complex(2, 3) // real, imaginary
  • strings - sequences of bytes
  • ASCII and UNICODE
  • each character has to be encoded
    • ASCII: 7 or 8 bits: ‘A’ = 0x41
    • UNICODE: 32-bit long code (2 to 32 characters can be represented)
    • UTF-8: variable length - 8-bit codes same as ASCII; default in Go
      • code point (or “rune” in Go): term for a unicode character
  • string
    • sequence of arbitrary bytes
    • array of runes
    • read-only (immutable)
    • string literal - between “”
  • Unicode package
    • IsDigit(r rune)
    • IsSpace(r rune)
    • IsLetter(r rune)
    • IsLower(r rune)
    • IsPunct(r rune)
    • ToUpper(r rune)
    • ToLower(r rune)
  • Strings package - looks string as a whole
    • Compare(a, b) - lexicographical comparison; returns -1, 0, 1
    • Contains(s, substr) - returns true/false
    • HasPrefix(s, prefix) - returns true/false
    • Index(s, substring) - returns the index of the 1st instance
    • Replace(s, old, new, n) - returns a copy of s where first n instances of old are replaced by new
    • ToLower(s) - returns new string
    • ToUpper(s) - returns new string
    • TrimSpace(s) - returns new string
    • Go Walkthrough: bytes + strings packages
  • Strconv package (string conversion)
    • Atoi(s) - convert string to int
    • Itoa(n) - converts int to string
    • FormatFloat()
    • ParseFloat()
  • Constants
    • expressions whose value is known in compile time
    • values inferred from right hand side
    • iota - function used to generate related but distinct constants (e.g. days of week) - value is not important, we don’t care about value; like an enumeration; each const is assigned to a unique integer
  • Control flow - determines the order of execution of statements
    • if statement
    • for loops
      • iterates while condition is true; condition is always required
      • may have initialization (executed only once) and update (executed at the end of each loop):
for ;; { }
    • switch 
      • contains tags (labels)
    • tagless switch
    • break - exits the containing loop
    • continue - skips the current iteration

Week 3 - Composite Data Types


  • Composite data types - aggregate other data types

Arrays

  • fixed-length series of elements of a chosen type
  • in compile time is known the length of it - compiler knows how much memory is needed
  • each element is indexed by using subscript notation - index ([ ])
  • indices start from 0
  • elements are initialized to zero value of the type (0 for integers, empty string for strings etc...)
var x[5] int // array of 5 integers
x[0] = 2
fmt.Printf(x[1]) // 0

  • Array literal
    • predefined set of values that make up an array
    • used to initialize an array
    • length of literal must be length of array
var x[5] int = [5] {1, 2, 3, 4, 5}
  • ... (three dots) - keyword; used to express the size of an array literal inferred from the list of values; in the previous example:
x := [...] {1, 2, 3, 4, 5} //  size of the array literal is 5 

  • iterating through array: use a for loop with range keyword; range returns two values: index and value at that index
x := [3] int {1, 2, 3}
for i, v := range x {
   fmt.Printf("index = %d; element value = %d", i, v)
}

  • Only slices, channels and arrays can be used in for-range.


Slices

  • lots of time are used instead of arrays
  • there must be some underlying array which is a basis of the slice
  • slice is a window on the underlying array (larger or of the same size)
  • can change their size; have variable size, up to the size of array
  • has 3 properties:
    • Pointer - indicates the start of the slice; points to the element of the array where slice starts
    • Length - number of element in the slice
    • Capacity - max number of elements; from start of slice to the end of array; e.g. if slice starts at 10th element of the array which has 100 elements => capacity is 90
  • colon (:) is used between m and n
    • m - index of (pointer to) the 1st element in the array which is IN the slice 
    • n - index of (pointer to) the 1st element in the array which is NOT in the slice
arr := [...]{"a", "b", "c", "d", "e", "f", "g", }
slice1 := arr[1:3] // {"b", "c",}
slice2 := arr[2:5] // {"c", "d", "e",}

  • len() - returns the length of the slice
  • cap() - returns the capacity of the slice (number of elements in the underlying array from the beginning of the slice to the end of the array)
a2 := [3]string{"a", "b", "c"}
slice21 := a2[0:1] // { "a" }
fmt.Println(len(slice21), cap(slice21)) // 1, 3
  • writing in the element of slice writes actually to the underlying array
  • overlapping slices can refer to the same element of the array; writing in one slice changes another slice 
  • slice literals
    • can be used to initialize a slice
    • creates the underlying array and creates a slice to reference it
    • slice points to the beginning of the array and length is  capacity
slice := [] int {1, 2, 3}

Variable Slices

  • 3rd way to make slice: make() function
    • 2-argument version: type (elements initialized to 0, empty string etc...), length (equal to capacity): 
slice :=  make([]int, 10)
    • 3-argument version: specify length and capacity separately: e.g. 10 is length and 15 a capacity
slice := make([]int, 10, 15) 
  • size of the slice can be increased by append()
    • adds elements at the end of the slice
    • inserts into underlying array
    • increases the size of the array if necessary
    • we can keep appending as we wish
slice := make([]int, 0, 3) // length slice is 0 but the size of underlying array is 3

slice := append(slice, 100) // 100 is the number (element) we want to add to the slice

Hash Tables

  • contains key-value pairs
  • each value is associated with the unique key
  • hash function
    • used to compute a slot for a key
    • its argument is the key, the output is the slot in the "array"
    • it's never called explicitly, it's part of the hash table implementation
    • e.g. {"Joe", "x" }, {"Jane", "y" }, {"Pat", "z" }. Hash function takes keys (names) and outputs e.g. slots 3, 1, 5 (indexes in the array/slice where values are stored)
    • we never use indexes/slot numbers but only keys e.g. hashTable["Jane"] would return "y"
  • access is in constant time
  • naming is useful as it's easier to use keys (e.g. names) rather than indexes (numbers)
  • advantages: 
    • faster lookup than list: hash table has constant time - it's always the same, no matter how many key-value pairs is in the table; list has linear lookup time - the more elements in list, the more time it takes to find some particular element
    • arbitrary keys, not integers like in slices or arrays; keys have meaning
  • disadvantages:
    • may have collisions: when hash function maps two different keys into the same slot
      • they are rare; hash functions are made in such way to minimize them

Maps

  • Golang's implementation of hash table
var mapId map[string]int // string is the key type and int is the value type
idMap = make(map[string]int) // creating a (empty) map
  • we can define map literal used to initialize a new map:
idMap := map[string]int {"joe": 123}
  • accessing maps
    • referencing a value with [key]: idMap["Joe"]
      • returns 0 if key is not present
    • to add a new key-value pair (or modifying the value of the existing key): idMap["Jane"] = 456
    • to delete a key-value pair: delete(idMap, "Joe")
  • map functions
    • two-value assignment tests for existence of the key; if "Joe" key exists, p (presence of the key) will be true and id will be its value; if we don't care for the value, we can use "_" as the name instead of "id"
id, p := idMap["Joe"]
    • len() returns number of key-value pairs in the map
  • Iterating through map
    • use for loop with the range keyword
    • two-value assignment with range
for key, value := range idMap {
   fmt.Println(key, value)
}

Structs

  • aggregate/composite data type
  • groups data of various types
type Person struct{
   name string
   address string
   phone string
}

var p1 Person
  • each property is called a field
  • to access fields (read/write) - use dot notation:
p1.name = "Joe"
x1 = p1.address

  • initializing structs
    • can use new()
      • initializes fields to 0 
p1 := new (Person)
    • by using a struct literal
p1 := Person { 
   name : "Joe", 
   address : "A street", 
   phone : "123"
}

Week 4 - Protocols and Formats

RFCs

  • definitions of Internet protocols and formats
  • examples:
    • HTML
    • URI 
    • HTTP
  • Go provides protocol packages
    • contain functions that encode/decode protocol format
  • "net/http"
    • web communication protocol
    • e.g. http.Get(www.uci.edu)
  • "net"
    • TCP/IP  and socket programming
    • e.g. net.Dial("tcp", "uci.edu:80")
  • JSON
    • JavaScript Object Notation
    • Format to represent structured information
    • attribute-value pairs
      • naturally correlate to structs/maps
    • basic value types: bool, number, string, array, object
      • can be combined hierarchically
    • Go struct:
p1 :=  Person(name: "Joe", addr: "a street", phone: "123")
    • Equivalent JSON object:
{ "name": "Joe", "addr": "a street", "phone": "123" }

JSON

  • all is UNICODE
  • human readable
  • fairly compact
  • types can be combined recursively
    • arrays of structs, structs of structs...
  • Go package: "encoding/json"
  • JSON marshalling
    • generating JSON representation from an object 

type Person struct{
   name string
   addr string
   phone string
}

p1 := Person(name: "Joe", addr: "a st.", phone: "123")
barr, err := json.Marshal(p1)


  • Marshal() returns JSON representation as []byte
    • err is nil if there was no error
    • barr is byte array that contains JSON representation
  • Unmarshal() converts a JSON []byte into Go object
    • second argument is the address of the object into which JSON will be deserialized; its fields have to match JSON object properties (and the whole structure); object must "fit" JSON 
var p2 Person
err := json.Unmarshal(barr, &p2)


File Access, ioutil

  • File access is liner, not random access
  • files used to be stored on physical tapes
    • mechanical delay when reading as file beginning is on one end of the tape and end on the other
  • physical disks still have linear access
  • RAM (flash memory) has random access
  • basic file operations:
    • Open - get handle for access
    • Read - read bytes into []byte
    • Write - write []byte into file
    • Close - release handle
    • Seek - move read/write head
  • there is more than one package in Go which handles file access, ioutil [1] is one of them
  • ioutil.ReadFile()
    • dat is []byte filled with the content of the entire file
    • explicit open/close are not needed
    • large files can cause a problem: if their size is same or larger than available RAM memory e.g. loading 8GB file to 8GB RAM
dat, e := ioutil.ReadFile("text.txt")
  • ioutil.WriteFile()
    • creates a new file
    • 3rd arg is a read/write permission, 0777 = every user can read/write

dat = "Hello, world"
err := ioutil.WriteFile("outfile.txt", dat, 0777)

File Access, os

  • ioutil provides simple utility
  • for more control over file access, use os package
  • os.Open()
    • opens a file
    • returns file descriptor
  • os.Close()
    • closes a file
  • os.Read()
    • reads from a file into a byte array ([]byte)
    • we can control how much to read
  • os.Write()
    • writes bytes into the file
    • writes as much as byte array is long
  • Opening and reading:
    • os.Read() returns number of bytes read; can be smaller than the size of barr
f, err := os.Open("dt.txt")
barr := make([]byte, 10)
nb, err := f.Read(barr)
f.Close()
  • File Create / Write
    • os.Write() - writes byte array
    • os.WriteString() - writes Unicode sequence
f, err := os.Create("outfile.txt")
barr := []byte {1, 2, 3}
nb, err := f.Write(barr)
nb, err := f.WriteString("Hi")



Bonus 

Project/Code Organization

Naming Conventions

Unit Testing



References:

https://golang.org/doc/