Functions are first-class values in Go: like other values, function values have types, and they may be assigned to variables or passed to or returned from functions. A function value may be called like any other function. For example:
func square(n int) int { return n * n } func negative(n int) int { return -n } func product(m, n int) int { return m * n } f := square fmt.Println(f(3)) // "9" f = negative fmt.Println(f(3)) // "-3" fmt.Printf("%T\n", f) // "func(int) int" f = product // compile error: can't assign f(int, int) int to f(int) int
The zero value of a function type is nil
. Calling a nil
function value causes a panic:
var f func(int) int f(3) // panic: call of nil function
Function values may be compared with nil
:
var f func(int) int if f != nil { f(3) }
but they are not comparable, so they may not be compared against each other or used as keys in a map.
Function values let us parameterize our
functions over not just data, but behavior too. The standard
libraries contain many examples.
For instance, strings.Map
applies a function to
each character of a string, joining the results to make another
string.
func add1(r rune) rune { return r + 1 } fmt.Println(strings.Map(add1, "HAL-9000")) // "IBM.:111" fmt.Println(strings.Map(add1, "VMS")) // "WNT" fmt.Println(strings.Map(add1, "Admix")) // "Benjy"
The findLinks
function from Section 5.2 uses a
helper function, visit
, to visit all the nodes in an HTML
document and apply an action to each one.
Using a function value, we can separate the logic for tree
traversal from the logic for the action to be applied to each node,
letting us reuse the traversal with different actions.
// forEachNode calls the functions pre(x) and post(x) for each node // x in the tree rooted at n. Both functions are optional. // pre is called before the children are visited (preorder) and // post is called after (postorder). func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } }
The forEachNode
function accepts two function
arguments, one to call before a node’s children are visited and one
to call after. This arrangement gives the caller a great deal
of flexibility.
For example, the functions startElement
and endElement
print the start and end tags of an HTML element like
<b>...</b>
:
var depth int func startElement(n *html.Node) { if n.Type == html.ElementNode { fmt.Printf("%*s<%s>\n", depth*2, "", n.Data) depth++ } } func endElement(n *html.Node) { if n.Type == html.ElementNode { depth-- fmt.Printf("%*s</%s>\n", depth*2, "", n.Data) } }
The functions also indent the output using another fmt.Printf
trick.
The *
adverb in %*s
prints a string
padded with a variable number of spaces.
The width and the string are provided by the arguments depth*2
and ""
.
If we call forEachNode
on an HTML document, like this:
forEachNode(doc, startElement, endElement)
we get a more elaborate variation on the output of our earlier
outline
program:
$ go build gopl.io/ch5/outline2 $ ./outline2 http://gopl.io <html> <head> <meta> </meta> <title> </title> <style> </style> </head> <body> <table> <tbody> <tr> <td> <a> <img> </img> ...
Exercise 5.7:
Develop startElement
and endElement
into a general HTML pretty-printer.
Print comment nodes, text nodes, and the attributes of each element
(<a href='...'>
). Use short forms like <img/>
instead of <img></img>
when an element has no children.
Write a test to ensure that the output can be parsed successfully.
(See Chapter 11.)
Exercise 5.8:
Modify forEachNode
so that the pre
and post
functions return a boolean result indicating whether to continue the
traversal.
Use it to write a function ElementByID
with the following signature
that finds the first HTML element with the specified id
attribute.
The function should stop the traversal as soon as a match is found.
func ElementByID(doc *html.Node, id string) *html.Node
Exercise 5.9:
Write a function expand(s string, f func(string) string) string
that
replaces each substring “$foo
” within s
by the text returned
by f("foo")
.