Thursday, March 7, 2024

Golang - Functions

  

Functions



Functions are reusable code blocks that you can call whenever you wish.

Format:

  func func_name (input_params) (result_params) {
  // your code here
  }


Every package-level function has a name, but avoid using Go-preserved names like 'init' and 'main'.

Functions can accept zero input parameters.

Ex:

func greeting() {
    fmt.Println("Hello")
}


Functions can accept numerous input arguments.

Ex:

func greetingWithNames(name1 string, name2 string) {
    fmt.Printf("Hello %s and %s\n", name1, name2)
}

func greetingWithNamesV2(name1, name2 string) {
    fmt.Printf("Hello %s and %s\n", name1, name2)
}


The "return" statement makes a function to stop and end instantly.

Ex:

func determineOddOrEven(number int) {
    if number%2 == 0 {
      fmt.Printf("%d is Even\n", number)

      return
    }

    fmt.Printf("%d is Odd\n", number)
}


A function may not return any value.

Ex:

func greeting() {
    fmt.Println("Hello")
}


A function may return a value without the need for parenthesis.

Ex:

func multiply(a int, b int) int {
    return a * b
}


A function may return multiple result values.

Ex:

func divide(a int, b int) (int, error) {
    if b > 0 {
        return a / b, nil
    }

    return 0, errors.New("Cannot be divided by 0")
}


If we need to return err, according to Go convention, the last parameter should be an error result value.

Go does not support function overloading. Refer to this article.

Do not print the error in the custom function. It is preferable to return the error to the caller and allow him to make a decision.


Input params and result values are copied



The values are copied regardless of whether they are supplied from the function caller or returned by the function to the caller.

As a result, any changes to the values within the functions will have no effect on the function callers' values.

Ex:

func main() {
    a, b := 2, 3
    c := add(a, b)

    fmt.Printf("main: a: %d, b: %d, c: %d\n", a, b, c)
}

func add(a int, b int) int {
    a += b

    fmt.Printf("func: a: %d, b: %d\n", a, b)
    return a
}


Result:

    func: a: 5, b: 3
    main: a: 2, b: 3, c: 5



Try to avoid changing package level variables



If you declare variables at the package level, any file in the same package can modify them.

It is not best practice since bugs are easy to introduce and hard to detect.

Ex:
main.go

package main

import "fmt"

var Counter int

func main() {
    fmt.Println("Hello")

    showCounterValue()

    increaseCounter()
    showCounterValue()
}


another_file.go

package main

import "fmt"

func increaseCounter() {
    Counter++
}

func showCounterValue() {
    fmt.Printf("Counter: %d \n", Counter)
}


The better way is to confine variables to a function.

Ex:

package main

import "fmt"

func main() {
    counter := 0
    fmt.Println("Hello")

    showCounterValue(counter)

    counter = increaseCounter(counter)
    showCounterValue(counter)
}

func increaseCounter(counter int) int {
    counter++
    return counter
}

func showCounterValue(counter int) {
    fmt.Printf("Counter: %d \n", counter)
}


* 'counter' is declared inside main function. Therefore, only main function can acess it.

* The function 'increaseCounter' and 'showCounterValue' can only get the value of 'counter' variable because their input parameters are copied.

* Use 'Errorf() to create and return an error value from the given format and arguments.


Naked Return



Go's return values may be named. If so, they are treated as variables defined at the top of the function.

A return statement without arguments returns the named return values. This is known as a "naked" return.

However, it is best to avoid using it because it reduces readability. Refer to this article.

Ex:

func divide(a int, b int) (int, error) {
    var (
      result int
      err    error
    )

    if b == 0 {
      err = fmt.Errorf("%d cannot be divided by %d", a, b)
      return result, err
    }

    result = a / b
    return result, err
}

func divideWithNamedReturn(a int, b int) (result int, err error) {
    if b == 0 {
      err = fmt.Errorf("%d cannot be divided by %d", a, b)
      return
    }

    result = a / b
    return
}



Passing different type of data



In Go, every value is copied when assigned to a variable or passed to a function.

MAP


In Go, the map value corresponds to the memory address of the "Map header".

So, even if it is copied, the values of two distinct variables will point to the same memory address.

As a result, changes to the passed map will have an effect on the caller's map.

Ex:

func main() {
    // Set up text mapping for English and Chinese.
    dict := map[string]string{
      "Hi":      "嗨",
      "Welcome": "歡迎",
      "Sorry":   "抱歉",
    }

    fmt.Printf("disc: %v\n", dict)

    updateDict(dict)
    fmt.Printf("disc: %v\n", dict)
}

func updateDict(dict map[string]string) {
    dict["Yo"] = "喲"
}


Result:

  disc: map[Hi:嗨 Sorry:抱歉 Welcome:歡迎]
  disc: map[Hi:嗨 Sorry:抱歉 Welcome:歡迎 Yo:喲]



Slice


In Go, the slice value point to the the "Slice Header" which track 'length', 'capacity' and the memory of the backing array.

As a result, any modifications to the passed slice will only affect the "Slice Header".

And nothing will change the caller's slice.

Ex:

func main() {
    names := []string{
      "Frank", "Jake",
    }

    fmt.Printf("slice: %v\n", names)

    appendSice(names)
    fmt.Printf("slice: %v\n", names)
}

func appendSice(names []string) {
    names = append(names, "Mary")
}


Result:

  slice: [Frank Jake]
  slice: [Frank Jake]


To synchronize the changes, we must first return the updated slice and then update the caller's slice.

Ex:

func main() {
    names := []string{
      "Frank", "Jake",
    }

    fmt.Printf("slice: %v\n", names)

    names = appendSice(names)
    fmt.Printf("slice: %v\n", names)
}

func appendSice(names []string) []string {
    names = append(names, "Mary")
    return names
}


Result:

  slice: [Frank Jake]
  slice: [Frank Jake Mary]


Array


When we assign one array to another, Go creates a new memory location for the new array.

As a result, the old and new arrays are not linked, and any modifications to the passed array will only affect the caller's array.

Tuesday, March 5, 2024

Golang - Struct

 

Struct



Unlike 'Slice' and 'Map', 'Struct' allows us to define our own type that holds different types of data. It is similar to a class in other object-oriented programming languages. We can store relevant data about a single concept in a single struct type.


Take the following example: person-related data such as 'name', 'age', and 'isMale' can be compiled into a single 'person' structure.



  type person struct {
    name   string
    age    int
    isMale bool
  }


Struct type is similar to a blueprint and can be used to build struct values.
Struct values resemble instances or objects in other object-oriented programming language.



  p1 := person{
    name:   "Jake",
    age:    10,
    isMale: true,
  }

  p2 := person{
    name:   "Mary",
    age:    20,
    isMale: false,
  }



More details



1. Structs fields are fixed at compile-time.


2. The zero value of 'struct' will be a struct value with all of its fields, each of which will have the zero value of its field type.

Ex:

type person struct {
    name   string
    age    int
    isMale bool
  }

  var p1 person

  fmt.Printf("P1: %#v\n", p1)


Result:
            
P1: main.person{name:"", age:0, isMale:false}


3. We do not need to provide all of the field values when initializing.

Ex:

  type person struct {
    name   string
    age    int
    isMale bool
  }

  p1 := person{
    name:   "Jake",
    isMale: true,
  }


4. We can also omit the field name when initializing, and Go will automatically initialize them by matching the field ordering to the values. However, it is not recommended.

Ex:

type person struct {
    name   string
    age    int
    isMale bool
  }

  p2 := person{
    "Mary",
    20,
    false,
  }


5. Struct, unlike Slice and Map, are bare values (similar to Array). So, when you copy one struct value to another, the changes in one will not effect the other.

Ex:

  type person struct {
    name   string
    age    int
    isMale bool
  }

  p1 := person{
    name:   "Jake",
    age:    10,
    isMale: true,
  }

  // Copy
  p2 := p1

  p2.name = "Mary"

  fmt.Printf("P1: %#v\n", p1)
  fmt.Printf("P2: %#v\n", p2)


Result:
            
  P1: main.person{name:"Jake", age:10, isMale:true}
  P2: main.person{name:"Mary", age:10, isMale:true}


6. If all of the struct fields are comparable, it can be compared using the equal operator.

Ex1:

  type person struct {
    name   string
    age    int
    isMale bool
  }

  p1 := person{
    name:   "Jake",
    age:    10,
    isMale: true,
  }

  // Copy
  p2 := p1

  // p2.name = "Mary"

  if p1 == p2 {
    fmt.Println("p1 and p2 are the same")
  } else {
    fmt.Println("p1 and p2 are not the same")
  }


Ex2:

  type person struct {
    name     string
    age      int
    isMale   bool
    nikename []string
  }

  p1 := person{
    name:   "Jake",
    age:    10,
    isMale: true,
  }

  // Copy
  p2 := p1

  if p1 == p2 {
    fmt.Println("p1 and p2 are the same")
  } else {
    fmt.Println("p1 and p2 are not the same")
  }


Result:
            
invalid operation: p1 == p2 (struct containing []string cannot be compared)



Embedding (Object-Oriented Composition)



Before introducing 'struct embedding', let us compare inheritance and composition of OOP. Refer to this article.

Inheritance is the 'is-a' relationship.


Take the following as an example, MountainBike is-a Bicycle; RoadBike is-a Bicycle. So they are all the same type 'Bicycle'.

Ex (Java):

  public class Bicycle {
    // the Bicycle class has three fields
    public int cadence;
    public int gear;
    public int speed;
  }

  public class MountainBike extends Bicycle {
    // the MountainBike subclass adds one field
    public int seatHeight;
  }

  public class RoadBike extends Bicycle {
    // the RoadBike subclass adds one field
    public string color;
  }


Composition is the 'has-a' relationship. 


Take the following as an example, Car has Engine; Motorcycle has Engine. So they all have their own types.

Ex (Java):

  public class Engine {
    public string type;
  }

  public class Car {
    //composition has-a relationship
    public Engine engine;

    public bool isAWD;
  }

  public class Motocycle {
    //composition has-a relationship
    public Engine engine;

    public string gloveColor;
  }


Go does not use 'Inheritance'. Go use 'Composition' and call it 'Embedding'.

Ex:

  type engine struct {
    engineType string
  }

  type car struct {
    // embedding field
    engine engine

    isAWD bool
  }

  type motorcycle struct {
    // embedding field
    engine engine

    gloveColor string
  }

  myCar := car{
    engine: engine{
        engineType: "V4",
    },
    isAWD: true,
  }

  myMotorcycle := motorcycle{
    engine: engine{
        engineType: "V1",
    },
    gloveColor: "red",
  }

  fmt.Printf("myCar: %#v\n", myCar)
  fmt.Printf("myMotorcycle: %#v\n", myMotorcycle)

  // How to get the field value from the embedding field
  fmt.Printf("myCar.engine: %#v\n", myCar.engine.engineType)


A struct field without a name is called an anonymous field.

When getting one field value of the anonymous composition field, we can omit the anonymous composition field name.

Ex:

  type engine struct {
    engineType string
  }

  type car struct {
    // embedding field
    engine engine

    isAWD bool
  }

  type motorcycle struct {
    // anonymous embedding field
    engine

    gloveColor string
  }

  myCar := car{
    engine: engine{
        engineType: "V4",
    },
    isAWD: true,
  }

  myMotorcycle := motorcycle{
    engine: engine{
        engineType: "V2",
    },
    gloveColor: "red",
  }

  fmt.Printf("myCar: %#v\n", myCar)
  fmt.Printf("myMotorcycle: %#v\n", myMotorcycle)

  // How to get the field value from the embedding field
  fmt.Printf("myCar.engine: %#v\n", myCar.engine.engineType)

  // How to get the field value from the anonymous embedding field
  fmt.Printf("myMotorcycle.engine: %#v\n", myMotorcycle.engine.engineType)
  fmt.Printf("myMotorcycle.engine: %#v\n", myMotorcycle.engineType)


How about the name of struct fields are the same with the one from the anonymous composition field (conflict)? Then we cannot omit the embedding field name.

Ex:

  type engine struct {
    engineType string
    // duplicate field name
    title string
  }

  type car struct {
    // anonymous embedding field
    engine engine

    isAWD bool
    // duplicate field name
    title string
  }

  myCar := car{
    engine: engine{
      engineType: "V4",
      title:      "exp1",
    },
    isAWD: true,
    title: "exp2",
  }

  fmt.Printf("myCar: %#v\n", myCar)

  // How to get the field value from the embedding field
  fmt.Printf("myCar.engine: %#v\n", myCar.engine.title)
  fmt.Printf("myCar.engine: %#v\n", myCar.title)



JSON Encoding and Decoding



What is JSON?

* JSON stands for JavaScript Object Notation
* JSON is a lightweight data-interchange format

The 'encoding/json' package in Go allows us to encode (marshal) or decode (unmarshal) data.

Only struct fields beginning with an uppercase letter can be exported by the json package.

Warning:
            
struct field "xxx" has json tag but is not exported


Field tags are part of a struct's type. Thus, they are compile-time constants.

Field tags associate a static string metatdata to a field.

Use 'Marshal' or 'MarshalIndent' to encode data.

Ex:

type permissions map[string]bool

type user struct {
    Name        string      `json:"username"`
    Password    string      `json:"-"`
    Permissions permissions `json:"perm,omitempty"`
}

func main() {
    users := []user{
      {
        "Frank", "1234", nil,
      },
      {
        "Jake", "4321", permissions{
          "admin": true,
        },
      },
    }

    out, err := json.MarshalIndent(users, "", "\t")
    if err != nil {
      fmt.Println("err:", err)
      return
    }

    fmt.Println(string(out))
}


Result:
            
  [
    {
      "name": "Frank"
    },
    {
      "name": "Jake",
      "perm": {
        "admin": true
    }
    }
  ]


Let us investigate the 'json:"perm,omitempty"' in the case above.

    json: 
        It is the key name that allows the JSON package to read this tag.

    "perm,omitempty"
        Is the value of the json tag, which is encased in double quotes and separated by commas.

Use 'Unmarshal' to decode the json string to struct, slice, or map.

Ex:

type permissions map[string]bool

type user struct {
    Name        string      `json:"username"`
    Permissions permissions `json:"perm"`
}

func main() {
    jsonStr := []byte{}

    scanner := bufio.NewScanner(os.Stdin)

    for scanner.Scan() {
      jsonStr = append(jsonStr, scanner.Bytes()...)
    }

    users := []user{}

    json.Unmarshal(jsonStr, &users)
}