GO - Part 4
Credit goes to - Master the fundamentals and advanced features of the Go Programming Language (Golang), on Udemy by Stephen Grider.
I have tried some snippets on my own and some are referring to the snippets developed while going through the course.
Code Repo: https://github.com/namitsharma99/goLangTutorials
Interfaces
- - - - - - - - -
Need:
Whenever there is a logic change in a function, it has to be re-written.
For example, maintaining 2 chatboxes :
A.
1. type hindiBot struct
2. func (hindiBot) getGreeting() string // return "namaste"
3. func printGreeting(hb hindiBot) // similar code - fmt.Println(hb.getGreeting())
B.
1. type englishBot struct
2. func (englishBot) getGreeting() string // return "hello"
3. func printGreeting(eb englishBot) // similar code - fmt.Println(eb.getGreeting())
Code --
package main
import "fmt"
type hindiBot struct {}
type englishBot struct {}
func main() {
}
//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string { // since no hb value is read
return "namaste!!"
}
// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
return "hello!!"
}
func printGreeting(hb hindiBot) {
fmt.Println(hb.getGreeting())
}
func printGreeting(eb englishBot) {
fmt.Println(eb.getGreeting())
}
ERROR::
$ go build main.go
# command-line-arguments
./main.go:26:6: printGreeting redeclared in this block
previous declaration at ./main.go:22:23
FIX 1:
package main
import "fmt"
type hindiBot struct {}
type englishBot struct {}
func main() {
hb := hindiBot{}
eb := englishBot{} // gives error
printGreeting(hb)
printGreeting(eb)
}
//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string { // since no hb value is read
return "namaste!!"
}
// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
return "hello!!"
}
// ERROR
/* func printGreeting(hb hindiBot) {
fmt.Println(hb.getGreeting())
}
func printGreeting(eb englishBot) {
fmt.Println(eb.getGreeting())
} */
// printGreeting redeclared in this block
// using single print method
func printGreeting(hb hindiBot) {
fmt.Println(hb.getGreeting())
}
ERROR::
$ go build main.go
# command-line-arguments
./main.go:13:16: cannot use eb (type englishBot) as type hindiBot in argument to printGreeting
FIX 2:
-- using interfaces
package main
import "fmt"
// the interface fix
type bot interface {
getGreeting() string
}
type hindiBot struct {}
type englishBot struct {}
func main() {
hb := hindiBot{}
eb := englishBot{}
printGreeting(hb)
printGreeting(eb)
}
//func (hb hindiBot) getGreeting() string {
func (hindiBot) getGreeting() string { // since no hb value is read
return "namaste!!"
}
// func (eb englishBot) getGreeting() string {
func (eb englishBot) getGreeting() string { // since no eb value is read
return "hello!!"
}
// ERROR
/* func printGreeting(hb hindiBot) {
fmt.Println(hb.getGreeting())
}
func printGreeting(eb englishBot) {
fmt.Println(eb.getGreeting())
} */
// printGreeting redeclared in this block
func printGreeting(b bot) {
fmt.Println(b.getGreeting())
}
O/P
- - -
$ ./main
namaste!!
hello!!
WORKING:
Interface identifies based on the func associated.
type bot interface {
getGreeting() string
}
This declares that any type, having getGreeting() func and returning the string, automatically becomes a member of this interface
Since hb and eb above, are now the members, they can call the printGreeting() func easily using the bot interface
Note:
------
Argument type can also be put in the clause along with the type check as follows :
type bot interface {
getGreeting(string, int) string
// and many other functions
}
- interfaces are not generic types
- interfaces are implicit, matched with signature clauses only
- interfaces just help in maintaining types, no further validation
net/ http
- - - - - - - -
package main
import "fmt"
import "net/http"
import "os"
func main() {
resp, err := http.Get("http://www.google.com")
if nil != err {
fmt.Println("Error: ", err)
os.Exit(1)
}
fmt.Println("response: ",resp)
}
O/P
- - -
$ ./main
response: &{200 OK 200 HTTP/1.1 1 1 map[Cache-Control:[private, max-age=0] Content-Type:[text/html; charset=ISO-8859-1] Date:[Wed, 20 May 2020 21:41:29 GMT] Expires:[-1] P3p:[CP="This is not a P3P policy! See g.co/p3phelp for more info."] Server:[gws] Set-Cookie:[1P_JAR=2020-05-20-21; expires=Fri, 19-Jun-2020 21:41:29 GMT; path=/; domain=.google.com; Secure NID=204=V-0Lr33CGR1OSs2-eLQShrsFqM23NpD66sBXPp4lJky4VKQ1LSL6Uoz6SpRpUuDB8HToPfN6YFbT_cgGjnO595t07MExURpcb5UeC_juxvM0dMZrQduvG8FUJ5gsIzCNGCWumrxNWyrEhntEvhUBw9rfjkE9w6u7zzoq3_05ltI; expires=Thu, 19-Nov-2020 21:41:29 GMT; path=/; domain=.google.com; HttpOnly] X-Frame-Options:[SAMEORIGIN] X-Xss-Protection:[0]] 0xc00011e0e0 -1 [] false true map[] 0xc0001ee000 <nil>}
Some more info on Interfaces
- - - - - - - - - - - - - - - - - - - - - - - - -
Reader Interface - provides common implementation to read any source as byte slice - []byte, which can work with anything further
package main
import "fmt"
import "net/http"
import "os"
func main() {
resp, err := http.Get("http://www.google.com")
if nil != err {
fmt.Println("Error: ", err)
os.Exit(1)
}
// fmt.Println("response: ",resp)
bs := make([]byte, 99999)
resp.Body.Read(bs)
fmt.Println("byte slice: ", bs)
fmt.Println("byte slice as string: ", string(bs))
}
O/P
---
< Many byte characters and huge response >
Another option to print response -
...
io.Copy(os.Stdout, resp.Body)
...
Writer Interface - opposite of Reader interface, takes byte slice and converts into desired format
io.Copy(os.Stdout, resp.Body) --> the params are implementation of Writer interface and Reader interface respectively
Copy() internally uses CopyBuffer()
package main
import "fmt"
import "net/http"
import "os"
import "io"
type logWriter struct {}
func main() {
resp, err := http.Get("http://www.google.com")
if nil != err {
fmt.Println("Error: ", err)
os.Exit(1)
}
// fmt.Println("response: ",resp)
// bs := make([]byte, 99999)
// resp.Body.Read(bs)
// fmt.Println("byte slice: ", bs)
// fmt.Println("byte slice as string: ", string(bs))
// io.Copy(os.Stdout, resp.Body)
lw := logWriter{}
io.Copy(lw, resp.Body)
}
func (logWriter) Write(bs []byte) (int, error) {
// return 1, nil
fmt.Println(string(bs))
fmt.Println("length ---- ", len(bs))
return len(bs), nil
}
o/p
- - -
< response body >
Channels and Go Routines
- - - - - - - - - - - - - - - - - - - - - - -
Example Code:
main.go
- - - -
package main
import "fmt"
import "net/http"
func main() {
links := []string {
"http://www.google.com",
"http://www.golang.org",
"http://www.stackoverflow.com",
"http://www.amazon.com",
}
for _, link := range links {
checkLink(link)
}
}
func checkLink(link string) {
_, err := http.Get(link)
if nil != err {
fmt.Println(link, " is down!!")
return
}
fmt.Println(link, " is up!!")
}
O/P
- - -
$ ./main
http://www.google.com is up!!
http://www.golang.org is up!!
http://www.stackoverflow.com is up!!
http://www.amazon.com is up!!
Note: As clear from the response, this is sequential and involves waiting. Now, need to see how to make the calls in parallel - Go Routines.
Go Routine lets the Main Routines knows, that there is a blocking function and Main should continue.
Syntax: just need to add go prefix against the function calls
go checkLink(link)
Go Scheduler takes care of the monitoring. Be default, it uses only one CPU core, but it can be customized to distribute the load.
Concurrency/ Parallelism can be achieved once multiple cores are involved.
Main routine doesn't give any priority to the child routines hence they are not executed. Therefore, need to use Channels.
It acts as a mediator to communicate between the Main routine and the child routines.
Channels are strictly defined per types, string channel can't support the int type.
How to send data with Channels
channel <- 9 : send the value 9 into channel
n <- channel : waiting for channel to get the value and assigning it further to the variable n
fmt.Println(<- channel) : waiting for channel to get the value and hence printing it afterwards
Code:
main.go
- - - - - -
package main
import "fmt"
import "net/http"
func main() {
links := []string {
"http://www.google.com",
"http://www.golang.org",
"http://www.stackoverflow.com",
"http://www.amazon.com",
}
c := make(chan string) // need to pass this channel in function call
for _, link := range links {
go checkLink(link, c)
}
fmt.Println(<- c)
}
func checkLink(link string, c chan string) {
_, err := http.Get(link)
if nil != err {
fmt.Println(link, " is down!!")
c <- "down !!"
return
}
fmt.Println(link, " is up!!")
c <- "up!!"
}
O/P
- - -
$ ./main
http://www.google.com is up!!
up!!
One issue - only one url is checked...
Because, Main Routine proceeds as soon as one of the child routines comes back.
Message receiving is also a blocking code.
Making the channel receiver again in Main routine -
...
fmt.Println(<- c)
fmt.Println(<- c)
...
O/P
- - -
$ ./main
http://www.google.com is up!!
up!!
http://www.stackoverflow.com is up!!
up!!
All results can be seen if same number of receivers are created, but hangs if any additional is there !!
Quick hack -
...
for i :=0; i< len(links); i++ {
fmt.Println(<- c)
}
...
How to repeat the Routines?
- - - - - - - - - - - - - - - - - - - - - - -
Passing links instead of values, and handling at the receiver ends
package main
import "fmt"
import "net/http"
func main() {
links := []string {
"http://www.google.com",
"http://www.golang.org",
"http://www.stackoverflow.com",
"http://www.amazon.com",
}
c := make(chan string) // need to pass this channel in function call
for _, link := range links {
go checkLink(link, c)
}
// fmt.Println(<- c)
// fmt.Println(<- c)
// for i :=0; i< len(links); i++ {
// fmt.Println(<- c)
// }
for {
go checkLink(<- c, c)
}
}
func checkLink(link string, c chan string) {
_, err := http.Get(link)
if nil != err {
fmt.Println(link, " is down!!")
// c <- "down !!"
c <- link
return
}
fmt.Println(link, " is up!!")
// c <- "up!!"
c <- link
}
O/P
- - -
< Going on and on... forever>
Alternatively, making it more readable --
...
for l := range c {
go checkLink(l, c)
}
...
How to add a pause in between the checks?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Use Time package, sleep function
Avoid adding pauses in the main routines, it's a bad way - blocking and queuing
Use function literals, i.e. unnamed functions
func main() {
links := []string {
"http://www.google.com",
"http://www.golang.org",
"http://www.stackoverflow.com",
"http://www.amazon.com",
}
c := make(chan string) // need to pass this channel in function call
for _, link := range links {
go checkLink(link, c)
}
// Alternative syntax
for l := range c {
// time.Sleep(5*time.Second) // 5 seconds, bad way - blocking and queuing
// go checkLink(l, c)
go func() { // function literals, like unnamed and anonymous functions
time.Sleep(5*time.Second)
go checkLink(l, c)
} ()
}
}
Issue: l is not a trustworthy variable to monitor for the Main routine, as it's value might have changed.
Output can be like first run happening fine for all URLs but after that stuck with one url's copy. (Go being pass by copy)
Fix, pass l from the range -
...
for l := range c {
go func(link string) {
time.Sleep(5*time.Second)
go checkLink(link, c)
} (l)
}
...
O/P AS EXPECTED -
- - - - - - - - -
$ ./main
http://www.google.com is up!!
http://www.stackoverflow.com is up!!
http://www.amazon.com is up!!
http://www.golang.org is up!!
http://www.google.com is up!!
http://www.stackoverflow.com is up!!
http://www.amazon.com is up!!
http://www.golang.org is up!!
http://www.google.com is up!!
http://www.stackoverflow.com is up!!
http://www.amazon.com is up!!
^C