One of Go's syntactical tour de force is via its support for higher-order functions as is found in dynamic languages such as Python or Ruby. As we will see in this chapter, a function is also a typed entity with a value that can be assigned to a variable. In this chapter, we are going to explore functions in Go covering the following topics:
In Go, functions are first-class, typed programming elements. A declared function literal always has a type and a value (the defined function itself) and can optionally be bound to a named identifier. Because functions can be used as data, they can be assigned to variables or passed around as parameters of other functions.
Declaring a function in Go takes the general form illustrated in the following figure. This canonical form is used to declare named and anonymous functions.
The most common form of function definition in Go includes the function's assigned identifier in the function literal. To illustrate this, the following table shows the source code of several programs with definitions of named functions with different combinations of parameters and return types.
Code |
Description |
package main import ( "fmt" "math" )func printPi() { fmt.Printf("printPi() %v ", math.Pi) } func main() { printPi() } ("fmt" "math" ) func printPi() { fmt.Printf("printPi() %v ", math.Pi) } func main() { printPi() }
golang.fyi/ch05/func0.go |
A function with the name identifier |
package main import "fmt" func avogadro() float64 { return 6.02214129e23 } func main() { fmt.Printf("avogadro() = %e 1/mol ", avogadro()) }
golang.fyi/ch05/func1.go |
A function named |
package main import "fmt" func fib(n int) { fmt.Printf("fib(%d): [", n) var p0, p1 uint64 = 0, 1 fmt.Printf("%d %d ", p0, p1) for i := 2; i <= n; i++ { p0, p1 = p1, p0+p1 fmt.Printf("%d ",p1) } fmt.Println("]") } func main() { fib(41) }
golang.fyi/ch05/func2.go |
This defines the function |
package main import ( "fmt" "math" ) func isPrime(n int) bool { lim := int(math.Sqrt (float64(n))) for p := 2; p <= lim; p++ { if (n % p) == 0 { return false } } return true } func main() { prime := 37 fmt.Printf ("isPrime(%d) = %v ", prime, isPrime(prime)) }
golang.fyi/ch05/func3.go |
The last example defines the |
Function signature
The set of specified parameter types, result types, and the order in which those types are declared is known as the signature of the function. It is another unique characteristic that help identify a function. Two functions may have the same number of parameters and result values; however, if the order of those elements are different, then the functions have different signatures.
Normally, the name identifier, declared in a function literal, is used to invoke the function using an invocation expression whereby the function identifier is followed by a parameter list. This is what we have seen throughout the book so far and it is illustrated in the following example calling the fib
function:
func main() { fib(41) }
When, however, a function's identifier appears without parentheses, it is treated as a regular variable with a type and a value as shown in the following program:
package main import "fmt" func add(op0 int, op1 int) int { return op0 + op1 } func sub(op0, op1 int) int { return op0 - op1 } func main() { var opAdd func(int, int) int = add opSub := sub fmt.Printf("op0(12,44)=%d ", opAdd(12, 44)) fmt.Printf("sub(99,13)=%d ", opSub(99, 13)) }
golang.fyi/ch05/functype.go
The type of a function is determined by its signature. Functions are considered to be of the same type when they have the same number of arguments with the same types in the same order. In the previous example the opAdd
variable is declared having the type func (int, int) int
. This is the same signature as the declared functions add
and sub
. Therefore, the opAdd
variable is assigned the add
function variable. This allows opAdd
to be invoked as you would invoke the add
function.
The same is done for the opSub
variable. It is assigned the value represented by the function identifier sub
and type func (int, int)
. Therefore, opSub(99,13)
invokes the second function, which returns the result of a subtraction.
The last parameter of a function can be declared as variadic (variable length arguments) by affixing ellipses (…
) before the parameter's type. This indicates that zero or more values of that type may be passed to the function when it is called.
The following example implements two functions that accept variadic parameters. The first function calculates the average of the passed values and the second function sums up the numbers passed in as arguments:
package main import "fmt" func avg(nums ...float64) float64 { n := len(nums) t := 0.0 for _, v := range nums { t += v } return t / float64(n) } func sum(nums ...float64) float64 { var sum float64 for _, v := range nums { sum += v } return sum } func main() { fmt.Printf("avg([1, 2.5, 3.75]) =%.2f ", avg(1, 2.5, 3.75)) points := []float64{9, 4, 3.7, 7.1, 7.9, 9.2, 10} fmt.Printf("sum(%v) = %.2f ", points, sum(points...)) }
golang.fyi/ch05/funcvariadic.go
The compiler resolves the variadic parameter as a slice of type []float64
in both the preceding functions. The parameter values can then be accessed using a slice expression as shown in the previous example. To invoke functions with variadic arguments, simply provide a comma-separated list of values that matches the specified type as shown in the following snippet:
fmt.Printf("avg([1, 2.5, 3.75]) =%.2f ", avg(1, 2.5, 3.75)))
When no parameters are provided, the function receives an empty slice. The astute reader may be wondering, "Is it possible to pass in an existing slice of values as variadic arguments?" Thankfully, Go provides an easy idiom to handle such a case. Let's examine the call to the sum
function in the following code snippet:
points := []float64{9, 4, 3.7, 7.1, 7.9, 9.2, 10} fmt.Printf("sum(%v) = %f ", points, sum(points...))
A slice of floating-point values is declared and stored in variable points
. The slice can be passed as a variadic parameter by adding ellipses to the parameter in the sum(points...)
function call.
Go functions can be defined to return one or more result values. So far in the book, most of the functions we have encountered have been defined to return a single result value. In general, a function is able to return a list of result values, with diverse types, separated by a comma (see the previous section, Function declaration).
To illustrate this concept, let us examine the following simple program which defines a function that implements an Euclidian division algorithm (see http://en.wikipedia.org/wiki/Division_algorithm). The div
function returns both the quotient and the remainder values as its result:
package main import "fmt" func div(op0, op1 int) (int, int) { r := op0 q := 0 for r >= op1 { q++ r = r - op1 } return q, r } func main() { q, r := div(71, 5) fmt.Printf("div(71,5) -> q = %d, r = %d ", q, r) }
golang.fyi/ch05/funcret0.go
The
return
keyword is followed by the number of result values matching (respectively) the declared results in the function's signature. In the previous example, the signature of the div
function specifies two int
values to be returned as result values. Internally, the function defines int
variables p
and r
that are returned as result values upon completion of the function. Those returned values must match the types defined in the function's signature or risk compilation errors.
Functions with multiple result values must be invoked in the proper context:
This is illustrated in the following source snippet:
q, r := div(71, 5) fmt.Printf("div(71,5) -> q = %d, r = %d ", q, r)
In general, the result list of a function's signature can be specified using variable identifiers along with their types. When using named identifiers, they are passed to the function as regular declared variables and can be accessed and modified as needed. Upon encountering a return
statement, the last assigned result values are returned. This is illustrated in the following source snippet, which is a rewrite of the previous program:
func div(dvdn, dvsr int) (q, r int) { r = dvdn for r >= dvsr { q++ r = r - dvsr } return }
golang.fyi/ch05/funcret1.go
Notice the return
statement is naked; it omits all identifiers. As stated earlier, the values assigned in q
and r
will be returned to the caller. For readability, consistency, or style, you may elect not to use a naked return
statement. It is perfectly legal to attach the identifier's name with the return
statement (such as return q, r
) as before.
18.119.133.160