Chapter 1 Tutorial
Exercise 1.1: Modify theecho
program to also print os.Args[0]
, the name of the command that invoked it.
// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// 1.1 prints itself and its command-line arguments.
package main
import (
"fmt"
"os"
"strings"
)
func main() {
fmt.Println(strings.Join(os.Args, " "))
}
Exercise 1.2: Modify theecho
program to print the index and value of each of its arguments, one per line.
// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// 1.2 prints the index and value of each of its arguments, one per line.
package main
import (
"fmt"
"os"
)
func main() {
for i, v := range os.Args[1:] {
fmt.Println(i, v)
}
}
Exercise 1.3: Experiment to measure the difference in running time between our potentially inefficient versions and the one that usesstrings.Join
. (Section 1.6 illustrates part of the time package, and Section 11.4 shows how to write benchmark tests for systematic performance evaluation.)
// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// 1.3 measure the time of different implementation of echo
package main
import (
"fmt"
"os"
"strings"
"time"
)
func echo1() string {
start := time.Now()
defer func() {
fmt.Printf("echo1: %v ns\n", time.Since(start).Nanoseconds())
}()
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = " "
}
return s
}
func echo2() string {
start := time.Now()
defer func() {
fmt.Printf("echo2: %v ns\n", time.Since(start).Nanoseconds())
}()
s, sep := "", ""
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
return s
}
func echo3() string {
start := time.Now()
defer func() {
fmt.Printf("echo3: %v ns\n", time.Since(start).Nanoseconds())
}()
return strings.Join(os.Args[1:], " ")
}
func main() {
echo1()
echo2()
echo3()
}
// go run main.go a b c d e f g h i j k l m n o p q r s t u v w x y z
// output:
// echo1: 4743 ns
// echo2: 2495 ns
// echo3: 1097 ns
Exercise 1.4: Modifydup2
to print the names of all files in which each duplicated line occurs.
// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// 1.4 prints the count and text of lines that appear more than once
// in the input. It reads from stdin or from a list of named files.
// It also print the names of all files in which each duplicated line occurs.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string][]string)
files := os.Args[1:]
if len(files) == 0 {
countLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
continue
}
countLines(f, counts)
f.Close()
}
}
for line, files := range counts {
if len(files) > 1 {
fmt.Printf("%d\t%s\t%s\n", len(files), line, files)
}
}
}
func countLines(f *os.File, counts map[string][]string) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()] = append(counts[input.Text()], f.Name())
}
// NOTE: ignoring potential errors from input.Err()
}
Exercise 1.5: Change the Lissajous program’s color palette to green on black, for added authenticity. To create the web color#RRGGBB, usecolor.RGBA{0xRR, 0xGG, 0xBB, 0xff}, where each pair of hexadecimal digits represents the intensity of the red, green, or blue component of the pixel.
var palette = []color.Color{color.White, color.RGBA{0, 0xff, 0, 0xff}}
Exercise 1.6: Modify theLissajous
program to produce images in multiple colors by adding more values topaletteand then displaying them by changing the third argument ofSetColorIndex
in some interesting way.
// ....
var colorIndex uint8 = blackIndex
if i%3 == 0 {
colorIndex = uint8(rand.Uint32()%5 + 1)
}
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), colorIndex)
}
// ...
Exercise1.7: The function callio.Copy(dst,src)
reads from src and writes to dst. Use it instead ofioutil.ReadAll
to copy the response body toos.Stdout
without requiring a buffer large enough to hold the entire stream. Be sure to check the error result ofio.Copy
.
// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Fetch prints the content found at each specified URL.
package main
import (
"fmt"
"net/http"
"os"
"io"
)
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
_, err = io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
}
}
Exercise 1.8: Modifyfetch
to add the prefix http:// to each argument URL if it is missing. You might want to usestrings.HasPrefix
.
// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Fetch prints the content found at each specified URL.
package main
import (
"fmt"
"net/http"
"os"
"io"
"io/ioutil"
"strings"
)
func main() {
for _, url := range os.Args[1:] {
if !strings.HasPrefix(url, "http://") {
url = "http://" + url
}
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
_, err = io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
Exercise 1.9: Modifyfetch
to also print the HTTP status code, found inresp.Status
.
// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Fetch prints the content found at each specified URL.
package main
import (
"fmt"
"net/http"
"os"
"io"
"io/ioutil"
)
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
_, err = io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
fmt.Printf("Status: %s\n",resp.Status)
}
}
Exercise 1.10: Find a web site that produces a large amount of data. Investigate caching by running fetchall twice in succession to see whether the reported time changes much. Do you get the same content each time? Modify fetchall to print its output to a file so it can be examined.
// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Fetchall fetches URLs in parallel and reports their times and sizes.
package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
"strings"
)
// reported elapse time changed with successive run and content is not the same
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch) // start a goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) // receive from channel ch
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan<- string) {
i, j := strings.Index(url, "."), strings.LastIndex(url, ".")
// in case of only one "." in url
if i == j {
i = 0
}
output, err := os.Create(url[i+1:j] + "-dump.html")
start := time.Now()
if err != nil {
ch <- fmt.Sprintf("while create output file: %v", err)
return
}
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err) // send to channel ch
return
}
nbytes, err := io.Copy(output, resp.Body)
resp.Body.Close() // don't leak resources
output.Close()
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
// ..whether the reported time changes much.
// Yes
// Do you get the same content each time?
// No
Exercise 1.11: Try fetchall with longer argument lists, such as samples from the top million web sites available atalexa.com. How does the program behave if a web site just doesn’t respond? (Section 8.9 describes mechanisms for coping in such cases.)
Get http://www.google.com: dial tcp: i/o timeout
or
Get http://www.google.com: EOF
Exercise 1.12: Modify the Lissajous server to read parameter values from the URL. For example, you might arrange it so that a URL like http://localhost:8000/?cycles=20 sets the number of cycles to 20 instead of the default 5. Use the strconv.Atoi function to convert the string parameter into an integer. You can see its documentation with go doc strconv.Atoi.
// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// Run with "web" command-line argument for web server.
// Lissajous generates GIF animations of random Lissajous figures.
package main
import (
"image"
"image/color"
"image/gif"
"io"
"math"
"math/rand"
"os"
)
//!-main
// Packages not needed by version in book.
import (
"log"
"net/http"
"time"
"strconv"
)
//!+main
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // first color in palette
blackIndex = 1 // next color in palette
)
var cycleParam int
func main() {
//!-main
// The sequence of images is deterministic unless we seed
// the pseudo-random number generator using the current time.
// Thanks to Randall McPherson for pointing out the omission.
rand.Seed(time.Now().UTC().UnixNano())
if len(os.Args) > 1 && os.Args[1] == "web" {
//!+http
handler := func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Print(err)
}
for k, v := range r.Form {
if k == "cycles" {
var err error
cycleParam, err = strconv.Atoi(v[0]);
if err != nil {
log.Print(err)
cycleParam = 0
}
}
}
lissajous(w)
}
http.HandleFunc("/", handler)
//!-http
log.Fatal(http.ListenAndServe("localhost:8000", nil))
return
}
//!+main
lissajous(os.Stdout)
}
func lissajous(out io.Writer) {
const (
cycles = 5 // number of complete x oscillator revolutions
res = 0.001 // angular resolution
size = 100 // image canvas covers [-size..+size]
nframes = 64 // number of animation frames
delay = 8 // delay between frames in 10ms units
)
var cyc float64
if cycleParam > 0 {
cyc = float64(cycleParam)
} else {
cyc = cycles
}
freq := rand.Float64() * 3.0 // relative frequency of y oscillator
anim := gif.GIF{LoopCount: nframes}
phase := 0.0 // phase difference
for i := 0; i < nframes; i++ {
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
img := image.NewPaletted(rect, palette)
for t := 0.0; t < cyc*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}
Notes
Goroutines: A goroutine is a concurrent function execution. A channelis a communication mechanism that allows one goroutine to pass values of a specified type to another goroutine. The function main runs in a goroutine and the go statement creates additional goroutines.
When one goroutine attempts a send or receive on a channel, it blocks until another goroutine attempts the corresponding receive or send operation, at which point the value is transferred and both goroutines proceed.
Having main do all the printing ensures that output from each goroutine is processed as a unit, with no danger of interleaving if two goroutines finish at the same time.
The server runs the handler for each incoming request in a separate goroutine so that it can serve multiple requests simultaneously.
Function literal: that is, an anonymous function defined at its point of use.
Control flow: The optional default case matches if none of the other cases does; it may be placed anywhere. Cases do not fall through from one to the next as in C-like languages (though there is a rarely used fallthrough statement that overrides this behavior).
Pointers: Go provides pointers, that is, values that contain the address of a variable. In some languages, notably C, pointers are relatively unconstrained. In other languages, pointers are disguised as ‘‘references,’’and there’s not much that can be done with them except pass them around. Go takes a position somewhere in the middle. Pointers are explicitly visible. The&operator yields the address of a variable, and the*operator retrieves the variable that the pointer refers to, but there is no pointer arithmetic.
Methods and interfaces: A method is a function associated with a named type; Go is unusual in that methods may be attached to almost any named type.