Chapter 5. Functions in Go

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:

  • Go functions
  • Passing parameter values
  • Anonymous functions and closures
  • Higher-order functions
  • Error signaling handling
  • Deferring function calls
  • Function panic and recovery

Go functions

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.

Function declaration

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.

Function declaration

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 printPi. It takes no parameter and returns no values. Notice when there is nothing to return, the return statement is optional.

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 avogadro. It takes no parameter but returns a value of type float64. Notice the return statement is required when a return value is declared as part of the function's signature.

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 fib. It takes parameter n of type int and prints the Fibonacci sequence for up to n. Again, nothing to return, therefore the return statement is omitted.

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 isPrime function. It takes a parameter of type int and returns a value of type bool. Since the function is declared to return a value of type bool, the last logical statement in the execution flow must be a return statement that returns a value of the declared type.

Note

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.

The function type

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.

Variadic parameters

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.

Function result parameters

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:

  • They must be assigned to a list of identifiers of the same types respectively
  • They can only be included in expressions that expect the same number of returned values

This is illustrated in the following source snippet:

q, r := div(71, 5) 
fmt.Printf("div(71,5) -> q = %d, r = %d
", q, r) 

Named result parameters

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.133.160