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

















































1 comment:

micheal pan said...

BE SMART AND BECOME RICH IN LESS THAN 3DAYS....It all depends on how fast 
you can be to get the new PROGRAMMED blank ATM card that is capable of
hacking into any ATM machine,anywhere in the world. I got to know about 
this BLANK ATM CARD when I was searching for job online about a month 
ago..It has really changed my life for good and now I can say I'm rich and 
I can never be poor again. The least money I get in a day with it is about 
$50,000.(fifty thousand USD) Every now and then I keeping pumping money 
into my account. Though is illegal,there is no risk of being caught 
,because it has been programmed in such a way that it is not traceable,it 
also has a technique that makes it impossible for the CCTVs to detect 
you..For details on how to get yours today, email the hackers on : (
atmmachinehackers1@gmail.com ). Tell your 
loved once too, and start to live large. That's the simple testimony of how 
my life changed for good...Love you all ...the email address again is ;
atmmachinehackers1@gmail.com