Sunday, 26 December 2021

Why do we need an accordant pace in computer systems

In the past, we always know clock is used to adjust the pace of the computer systems. But we don't understand why we need an accordant pace in computer systems. Why don't we just let the components in the computer do as much and fast as they can?

The reason is that the different components have dependency between each other. So if we image the whole computer systems is a succession program, of course we don't need accordant pace any more. But this will make the components in computer wait for each other's result to go on. So this can't make components work in parallel way.

So obviously clock has this advantage which is that it can make different components work in parallel way become possible.

Sunday, 28 November 2021

DO NOT use the return of append function to create new slice in Go

For Golang developers, it is very common to use append function to append new elements into slice. The common syntax is like below:

    var slice := make([]int, 0)
    slice = append(slice, 123)
And for some requirements, you may want to create a new slice based on an existing slice. We know the below the code will work perfectly.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Below is complete example code to show the result:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc000014050
The address of newSlice: 0xc00007a000
You can see the two slices have different addresses.
However you may think if we can simplify the code by appending value to the origin slice and assign the return value to the newSlice variable like below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
This looks simpler and cleaner, right? NO, this won't work! Let's see an example:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc00010a000
The address of newSlice: 0xc00010a000
See newSlice is pointing to the same address of slice?

This is because append function does not guarantee that it always returns a new slice. It only reallocate a new slice when the capacity of the slice in the first parameter is not enough to hold more data. So if you are expecting to use append function to get a new slice like above, you will probably mess up the origin slice and get the wrong output.
So to be safe, if you want to get a new slice based on an existing slice, you use the code below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Or below code if it is clearer.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, slice...)
    newSlice = append(newSlice, 4)
I encountered this problem when I am practicing LeetCode problem 797. 



Wednesday, 17 November 2021

How to add optional arguments in Go function without impacting the existing function calls

Unlike C# you can put "Optional" keyword to add optional arguments in function, there is no direct way to create optional parameters in Go functions. And there is no method overloading in Go either. Even so, if you really need to add optional arguments to an existing function, maybe because this function is in a common library which is called by different software modules and you don't want to update all the modules for this new argument change, there is still a way to implement optional arguments thanks to variadic arguments in Go functions.

Variadic arguments allow developer to put any number of trailing arguments as the input of functions. For example:

package main

import (
  "fmt"
)

func main() {
  fmt.Printf("1 + 2 = %d\n", add(1, 2))
  fmt.Printf("1 + 2 + 3 + 4 + 5 = %d\n", add(1, 2, 3, 4, 5))
}

func add(args ...int) int {
  sum := 0
  for i:=0; i < len(args); i++ {
    sum += args[i]
  }
  return sum
}
The output is:
1 + 2 = 3
1 + 2 + 3 + 4 + 5 = 15
With the help of variadic arguments, you can easily to implement optional arguments. For example, you have a function to generate characters in a common library for a game development. This function can generate characters with name and sex for now. 
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
}

func createCharactor(name string, sex string) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  fmt.Println("------ New Charactor Created ------")
}
The output is: 
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------
Now you want to add age argument to this function without impacting the existing function call. You can do like following:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15)
}

func createCharactor(name string, sex string, age ...int) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(age) > 0 {
    fmt.Printf("Age is: %d\n", age[0])
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
------ New Charactor Created ------
But the above code is not reusable to add other arguments with different data types. For example, what if you need to add title, email later? So in order to make the variadic argument more generic, we can use "interface{}" type as the variadic argument which gives the ability to add more arguments with different data types.

According to this, the above code can be refactored as below:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15, "Software Engineer", "kai@gmail.com")
  fmt.Println()
	
}

func createCharactor(name string, sex string, args ...interface{}) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(args) > 0 {
    fmt.Printf("Age is: %d\n", args[0].(int))
    fmt.Printf("Tile is: %s\n", args[1].(string))
    fmt.Printf("Email is: %s\n", args[2].(string))
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
Tile is: Software Engineer
Email is: kai@gmail.com
------ New Charactor Created ------

However, this solution does not convey the meaning of the optional arguments. So you either put a comment above the header to specify what it means for each optional argument, or comment where you call the functions, otherwise it will become very difficult to understand the code. So this solution is not recommended unless you have a very tight deadline to meet the delivery date and really have no time to refactor code.  

Sunday, 24 October 2021

Pointer Receiver vs Value Receiver

Receivers are same as parameters in function in Go. They are also passed by value when functions are called. Because of this, it is preferred to use pointers as receivers instead of passing values directly. This is because usually we use receivers to implement OOP programming style. If passed by value, the properties of the objects will not be able to update.

For example, we have an object called "person", it has age property. Initially when created, the person has initialised the age to 0, then we use method to change it to 18. See code below:

package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age int
}

func main() {
	person := Person{Name : "Leo"}
	fmt.Println(person)
	person.SetAge(18)
	fmt.Println(person)
	
}

func (p *Person) SetAge(age int) {
	p.Age = age
}
The output of the result is:
{Leo 0}
{Leo 18}
However if the receiver is passed by value like below:

func (p Person) SetAge(age int) {
	p.Age = age
}

Then the age property will not be updated. However if the receiver is passed by value like below:

{Leo 0}
{Leo 0}

Sunday, 3 October 2021

Variables and Data Types in Javascript

This is a study note about Javascript from https://www.geeksforgeeks.org/introduction-to-javascript-course-learn-how-to-build-a-task-tracker-using-javascript/

Javascript is untyped language. Once a variable is created in javascript using 'var', you can assign any other type of data to this variable.

var text = 'Hello World'
text = 123

Above code is valid in Javascript. Variable text can be assigned a string value and then later change to a number.

1. Difference between var and let in Javascript

Basically, 'var' is function scoped, however 'let' is block scoped.

https://www.geeksforgeeks.org/difference-between-var-and-let-in-javascript/

Let's use use 'let' for most of the cases because 'var' is confusing.

2. Number type in Javascript

The number type in Javascript contains both integer and floating point numbers. Besides these numbers, we also have 'special-numbers' in Javascript that are: 'infinity', '-Infinity' and 'NaN'. The 'NaN' denotes a computational error.

let num = 2; // integer
let num2 = 1.3; // floating point number
let num3 = Infinity; // Infinity
let num4 = 'something here too'/2; // NaN

3. String type in Javascript

There are three types of quotes in Javascript:

let str = "Hello There";
let str2 = 'Single quotes works fine';
let phrase = `can embed ${str}`;

There's no difference between 'single' and "double" quotes in Javascript. Backticks provide extra functionality as with the help of them we can embed variables inside them.

let variable = "Leo";

// embed a variable
alert( `Hello, ${variable}!` ); // Hello, Leo!

4, Undefined type in Javascript

The meaning of undefined is 'value is not assigned'.

let x;
console.log(x); // undefined

5, Object type in Javascript

Everything in Javascript is Object. There are two ways to create objects.

let person = new Object();
let person = {};

You can initialise object with properties like below:

let person = {
name: "Mukul",
age: 22,
}







Sunday, 13 June 2021

Mistakes That Should Not Happen Again

 Database:

DO NOT use "key" keyword as a column name, because it is a reserved keyword in MySQL.


Docker:

Clean docker using below command from time to time to release more memory:

docker system prune

Then type u and press "enter".





Monday, 12 April 2021

Channels in Go

 This is a study note for YouTube tutorial: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s


Channel basics

You can only create channels using make function.

ch := make(chan int) // chan int means create a integer channel

Below is an example how to use channel to transfer data between goroutines.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int)
wg.Add(2)
go func() {
i := <-ch
fmt.Println(i)
wg.Done()
}()
go func() {
ch <- 42
wg.Done()
}()
wg.Wait()
}

The result is:
42


Restricting data flow

Be noted that channel operation will block the goroutine when executing. If you have unequal receiver and sender, then the additional goroutines will be blocked and give runtime errors.

func main() {
ch := make(chan int)
wg.Add(3)
go func() {
i := <-ch
fmt.Println(i)
wg.Done()
}()
go func() {
ch <- 42
wg.Done()
}()
go func() {
ch <- 27
wg.Done()
}()
wg.Wait()
}

27 fatal error: all goroutines are asleep - deadlock!

You can use bidirectional communication using channel in goroutines.

func main() {
ch := make(chan int)
wg.Add(2)
go func() {
i := <-ch
fmt.Println(i)
ch <- 27
wg.Done()
}()
go func() {
ch <- 42
fmt.Println(<-ch)
wg.Done()
}()

wg.Wait()
}

42 27

Goroutines can automatically manage the directions of the communication. However in most cases, we would like to make channel in single directions, which means one goroutine is dedicated to be receiver and another be a sender. To do this, you can put channel as function parameter for goroutine to restrict the data flow. Because function parameter can be used to cast a bi channel to a single direction channel.

func main() {
ch := make(chan int)
wg.Add(2)
go func(ch <-chan int) { // Here actually cast bi channel to receiver channel
i := <-ch
fmt.Println(i)
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
wg.Done()
}(ch)

wg.Wait()
}

42

Now the first goroutine is restricted to receive-only, and the second is restricted to send-only.

Buffered channels

You can add a buffer for a channel by simply specifying the second parameter of make function with buffer size.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
i := <-ch
fmt.Println(i)
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
wg.Done()
}(ch)

wg.Wait()
}

27 will be buffered instead of throwing a "deadlock" error.



For...range loops with channels

The above code is not a correct way to handle buffered channels. "for...range" loops is preferred in this case.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for i := range ch {
fmt.Println(i)
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27 fatal error: all goroutines are asleep - deadlock!

You can see there is "deadlock" error again, but this time it is not because of the sender goroutine, it is because of the for range loop in the receiver goroutine. Because the receiver goroutine keeps trying to receive data even there is no data sent anymore.

To solve this problem, you can use "close(ch)" function to close channel in the sender goroutine if there is no data to send anymore

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for i := range ch {
fmt.Println(i)
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
close(ch)
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27

In fact, channel has a second return value which you can use for checking if the channel is open or closed.

var wg = sync.WaitGroup{}

func main() {
ch := make(chan int, 50)
wg.Add(2)
go func(ch <-chan int) {
for {
if i, ok := <-ch; ok {
fmt.Println(i)
} else {
break
}
}
wg.Done()
}(ch)
go func(ch chan<- int) {
ch <- 42
ch <- 27
close(ch)
wg.Done()
}(ch)

wg.Wait()
}

The result is:

42 27


Select statements

You can use a select statement to manage the workflow in a goroutine. Select statement is similar to switch statement, but select statement is only used for communications. The case statement refers to the communications on different channels.

var logCh = make(chan string)
var doneCh = make(chan struct{})

func main() {
go logger()

logCh <- "test1"
logCh <- "test2"
time.Sleep(100 * time.Millisecond)
doneCh <- struct{}{} // empty struct used as a signal here
}

func logger() {
for {
select {
case log := <-logCh:
fmt.Println(log)
case <-doneCh:
break
}
}
}


References: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Difference between "docker stop" and "docker kill"

 To stop a running container, you can use either "docker stop" or "docker kill" command to do so. Although it seems doin...