🐭

Learning golang 03 - Functions

Functions from the go tour

Functions and pointers

Functions can receive pointers. Any change to the pointer would change the value.

package main

import "fmt"


func change(v *int) {
    *v = 10
//  ^ required, mentioning that the base value is being changed
}

func main() {
    a := 12

    change(&a)
    //     ^ sending argument as pointer. Throws an error if not.

    fmt.Println(a)

}

Function closures

A function closure references variable from outside the function. Each closure is left to its own values

package main

import "fmt"

func adder() func(int) int {
    sum:=0 //note this variable this is key

    return func (x int) int {
        return sum += x
    }
}

func main(){
    pos, neg = adder(), adder()  // here the closure is returned with seperate sum variable each

    for i:=0;i<10;i++ {
        fmt.Println(
            pos(i),
            neg(-1*i)
        ) //notice that the values get added, the value from the previous iteration is stored in the su variable
    }
    
}

Methods

Go does not have objects but you can write methods on types. This is done using a special receiver argument. Receiver comes between the func keyword and the function name

A receiver does not directly change the underlying value. There is a section below about pointer receivers.

package main

import "fmt"

type Vertex struct{
    x,y int
}

func (v Vertex) Abs() float64 {
//     ^^^ Note this, this is the receiver argument comes between func key word and function name
return math.Sqrt(v.x * v.x + v.y * v.y )
}

func main(){
    v := Vertex{3,4}
    fmt.Println(v.Abs())
}

Methods are functions

Methods are functions with the receiver argument

Same program as above written as a function

package main

import "fmt"

type Vertex struct{
    x,y int
}

func Abs(v Vertex) float64{
    return math.Sqrt(v.x*v.x + v.y*v.y)
}

func main() {
    v = Vertex{1,2}
    fmt.Println(Abs(v))
}

A Method with a receiver can only be created in the same package

Methods can be declared on non struct types as well, for example,

package main

import "fmt"

type Myfloat float64   // non-struct type

func (f Myfloat) Abs() Myfloat {
    return f*f
}

func main() {
    var a Myfloat = 1.234

    fmt.Println(a.Abs())
}

Pointer Receivers in methods

Receivers can be pointers as well, This helps in mutating the values.

package main

import "fmt"

type Vertex struct{
    X,Y int
}

func (v *Vertex) change() {
    // ^^^ this is very important without it, a new instance of vertex is sent to the method, with those values. which means the changes done below won't reflect in the original value

    v.X = 1
    v.Y = 3
}

func main(){
    
    v := Vertex{10,20}
    v.change()
    fmt.Println(v) //see the changes here.

}   

here the method receives a pointer to the instance mutating the instance itself.

Interfaces

An interface type can hold any value that implements those methods

package main

import "fmt"

type AreaFinder interface{
        //      ^^^^ note the key word, means that this can hold any type that has the method below.   
    Area()
}

// below comes standard types
type Circle struct{
    R int
}

type Square struct{
    A int
}

//Random won't have the Area() method
type Random struct{
    A int
}


func (c Circle) Area(){
    fmt.Println(3 * c.R * c.R)
}

func (s Square) Area(){
    fmt.Println(s.A * s.A)
}

func main (){

    var a AreaFinder // Now 'a' can be assigned a Circle or a square but not a Random
    
    a = Circle{3} //this works
	
	a = Square{4} //this works as well 🤯
	
	a = Random{6}
	fmt.Println(a)

}

Interfaces for Methods that implement using pointers, expect only a pointer to that struct. if anything else is given, error is thrown.

even though this would be valued exactly the same way as a method

package main

import "fmt"

type F struct {
    a float64
}

type I interface {
    Abs()
}

func (f *F) Abs(){
    //  ^ note that this is a pointer receiver
    fmt.Println("Abs is called")
}


func main() {
    var g,h I

    g = &F{2} // this will work as we are assigning only pointer

    h = F{3}  // this will fail as this is not a pointer.
}

Interfaces are implemented implicitly This means that, there is no need to specify that a certain type implements said method.

Interfaces also take up the type that they are being assigned to. Imagine Interface being a tuple of value and type (value, type) and it dynamically varies based on the value supplied internally.

package main

type I interface{
    M()
}

type F float64

type T struct{
    S string
}

func ( f F) M(){
    fmt.Println(f)
}

func ( t *T ) M() {
    fmt.Println(t.S)
}

func main(){
    var i I
    describe(i) // This will print an interface with nil values
    
    i = &T{"Hello"}
    describe(i)
    i.M()

    i = F(1.2323)
    describe(i)
    i.M()

}

func describe(s I) {
    fmt.Println("(%v,%T)\n",s,s)
}

//Outputs:
/*
(<nil>, <nil>)
(&{Hello}, *main.T)
Hello
(1.2323, main.F)
1.2323
*/

Interfaces and Nil values

When implementing an interface, nil pointer exceptions need to be handled as that could happen

package main

import "fmt"

type I interface {
    M()
}

type T struct {
    S string
}

func (t *T) M() {
    
    // The below block is important to avoid future errors.
    if t == nil {
    
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}

func main() {
    var i I
    describe(i) // (<nil>, <nil>)

    var t *T
    i=t
    describe(i) // (<nil>, *main.T)
    i.M()

    i = &T{Hello}
    describe(i) //(&{hello}, *main.T)

}

func describe(i I){
    fmt.Println(i)
}

The empty interface

Empty interfaces are possible in go

interface{}

An empty interface may hold value of any type. (Every type implements at least zero methods.) empty interfaces are used to get in arguments of any type. For example fmt.Println takes in and empty interface type to make it accept any value.

package main

import "fmt"

func main() {
    var i interface{}
    describe(i)

    i = 42
    fmt.Println(i)

    i = "Suriya"
    fmt.Println(i)
}

func describe(i interface{}){
    fmt.Printf("(%v, %T)\n",i,i)
}

Type assertions

Type assertion provides access to the interface value’s underlying concrete value.

t:=i.(T)

This statement asserts that the interface value i holds the concrete type. If i does not hold a T, the statement will trigger a panic.

t, ok := i.(T)

If i holds a T, then t will be the underlying value and ok will be true. If not, ok will be false and it will be the zero value of type T, and no panic occurs.

package go

import "fmt"

func do(i interface{}) {

    switch v := i.(type){
        case int:
            fmt.Printf("Twice %v is %v",v,v*2)
        case string:
            fmt.Printf("%q is %v bytes long\n", v, len)
        default:
            fmt.Printf("I don't know the type %T!\n",v)
    }

}

Stringers

One of the most ubiquitous interfaces is stringer defined by the fmt package

type Stringer interface{
    String() string
}

The fmt package first searches for the Stringer interface in any type

package main

import "fmt"

type Person struct{
    Name string
    Age int
}

//below implementation is what would be picked by fmt this time for printing
func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}


func main(){
    a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}

    fmt.Println(a,z) //Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

}

Errors

Similar to Stringer, go programs require error() method to articulate errors

This is to show the error of the specific change.

it is implemented like so

type error interface{
    Error()
}

Programs would return error value based on something

i, err = strconv.Atoi("42")
if err != nil {
    fmt.Println("123")
}