Chapter 4. Functions

Chapter 4. Functions
Photo by Clark Van Der Beken / Unsplash

You've already seen func's multiple times so far, but let's take this chapter to understand them better and when to use them when automating work.

Functions are reusable work units for a program to use, and they save programmers from repeating themselves.

You might hear this paradigm as D.R.Y. when speaking with other coders, which stands for "Don't repeat yourself".

Functions are like small programs within your program. And GO, in particular, must always have the func main() {} within the main.go file.

The main func is to tell the compiler (go build) that this is where the code starts. Remember, computers execute code line by line, so it helps to tell them which line to begin with.

I.Parts of a function

Let's start with a basic function and try to understand what each part of it is called and what it is responsible for.

// ex. 1
package main 

func NameIs(name string){ // (1)
	fmt.Printf("Your name is: %s", name)
}

func main() {
	NameIs("Gopher") // (2)
}

(1) The characters succeeding the 'func' keyword give the name to the 'func'.

In this example, it is correct to say the function's name is, well, NameIs.

And the characters within the parentheses 'name string' is the accepted argument(s).

Note: There can be more than one argument that a function can to do work on.

(2) It Is known as the 'function call,' or the function is being 'called/invoked'. That is a fancy way of saying, "the function is being used".

I.a When to get func-e (funky)?

Frequently whenever you're writing code, you might get it working-- and that is fine and a noble goal on the first pass. However, on the second pass (immediately, once it is working), it's time to refactor, which means doing things like reducing repetition by using functions.

I.b Example

Suppose we say hello to a bunch of readers without using a function. Here's how repetitive it will look.

// ex. 2
package main 

func main() {
	fmt.Printf("Hello %s", "Gopher")
        fmt.Printf("Hello %s", "Gopher2")
	fmt.Printf("Hello %s", "Gopher3")
        fmt.Printf("Hello %s", "Gopher4")
	fmt.Printf("Hello %s", "Gopher5")
        fmt.Printf("Hello %s", "Gopher6")
	fmt.Printf("Hello %s", "Gopher7")
        fmt.Printf("Hello %s", "Gopher8")
	fmt.Printf("Hello %s", "Gopher9")
        fmt.Printf("Hello %s", "Gopher10")

}

Let's do a refactor to make it a bit DRYer.

// ex. 3
package main

import "fmt"

func greetReaders(n []string) {
	for _, name := range n {
		fmt.Printf("\nHello %s", name)
	}
}

func main() {
	rdrs := []string{
		"Gopher",
		"Gohper2",
		"Gopher3",
		"Gohper4",
		"Gopher5",
		"Gohper6",
		"Gopher7",
		"Gohper8",
		"Gopher9",
		"Gohper10",
	}
	greetReaders(rdrs)

}

II. Multiple different kinds of arguments

Suppose we wanted to print a reader's name and age. Then we'll need string type data and number type data. In GO the arguments can be a mix of different types like string and int, not just a mono-type.

// ex. 4 Multiple Argument types
package main

import "fmt"

func HappyBirthday(n string, age int) {
	fmt.Printf("Happy birthday %s, You're turning %d", n, age)
}

func main() {
	HappyBirthday("Go", 20)

}

III. return values

So far, the functions defined haven't used the 'return' keyword, which means they're only doing something with the data passed in but not giving it back.

Let's return to our even/odd examples and sort integer arrays into odd and even.

// ex. 5 Returning values
import "fmt"

func EvenOdd(n []int) ([]int, []int) {  // (1)
	var odd, even []int
	for _, num := range n {
		if num%2 == 0 {
			even = append(even, num)
		}
		if num%2 == 1 {
			odd = append(odd, num)
		}

	}
	return odd, even // (2)
}

func main() {
	num := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
	odd, even := EvenOdd(num) // (3) 
	fmt.Println("Odd: ", odd)
	fmt.Println("Even: ", even)

}

(2) The 'return' keywords say at the end of this function call, give the value to the caller. The big idea here is that the 'value' returned can be 'assigned' to variables, which can then be passed further along in the program.

Notice, in GO, that you can return multiple values and assign them at once in (3). However, this can be tricky, so it's not recommended as you're starting out.

(1) The astute reader probably caught the additional parentheses following the accepted arguments; ([]int, []int). The second set of parentheses is required when returning multiple values. It lets the reader know the types expected after a successful invocation of the func.

Similarly, you can return multiple different data types as well.

// ex. 6 Returning multiple values 
package main

import "fmt"

func diffVals(n string, age int) (string, int) { // (1)
	return n, age
}

func main() {
	name, age := diffVals("Gopher", 20)
	fmt.Printf("name: %s, age: %d", name, age)
}

(1) Shows that a string type and int type will be returned at the end of a successful function call.

As you write code to solve problems, your programs will be more extensive than 20 lines.

Having functions that return data makes it simpler to keep track of what's going on in your program for you and other readers.

IV. Local variables and Global Variables

In all our code so far, we've mainly been using local variables, and now it is time to discuss what that means and what global means.

The distinction is that a local variable and its value are only accessible within the block of code in which it exists. Whereas a global variable may be accessed outside the code block and across all your program files. In general, global variables are frowned upon in GO programs, but in other languages, they're not so frowned upon.

Let's see an example.

// ex. 7 Globals are tricky
package main

import "fmt"

// global variables
var Author = "Gopher"
var Age = 20

func LocalAuthor(name string, age int) {
	Author = name
	Age = age
	fmt.Printf("\nAuthor: %s, \nAge: %d", name, age)
}

func main() {
	LocalAuthor("Gopher2", 40)
	fmt.Printf("\nAuthor: %s, \nAge: %d", Author, Age)

}

Above, the global variables have their values reassigned within the function call of LocalAuthor, which might cause problems in other parts of the program if the expected values were "Gopher" and 20.

To get the expected behavior, define local variables within the function 'n' and 'a' for name and age, respectively.

// ex. 8 Locals are preferred
package main

import "fmt"

// global variables
var Author = "Gopher"
var Age = 20

func LocalAuthor(name string, age int) {
	n := name // don't reference the global variables
	a := age
	fmt.Printf("\nAuthor: %s, \nAge: %d", n, a)
}

func main() {
	LocalAuthor("Gopher2", 40)
	fmt.Printf("\nAuthor: %s, \nAge: %d", Author, Age)
}

V. Error handling

One of the few areas where GO receives complaints is error handling, which is often verbose and redundant. However, the idea is that errors should be explicitly handled and error situations described.

Let's set up a function that will return an 'error' type if the received argument is 'empty'.


package main

import (
	"errors"
	"fmt"
	"log"
)

// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
      // If a name isn't received then there's no one to greet and it should return an error
	if name == "" {
		return "", errors.New("Err: empty name")
	}
        // otherwise great the person and return a nil for error
	message := fmt.Sprintf("Hi, %v. Welcome!", name)
	return message, nil
}

func main() {
	// Set properties of the predefined Logger, including
	// the log entry prefix and a flag to disable printing
	// the time, source file, and line number.
	log.SetPrefix("greetings: ")
	log.SetFlags(0)

	// Request a greeting message.
	message, err := Hello("")
	// If an error was returned, print it to the console and
	// exit the program.
	if err != nil {
		log.Fatal(err)
	}

	// If no error was returned, print the returned message
	// to the console.
	fmt.Println(message)
}

Error handling is a must if you're going to be building functions that you intend to share with the world or a team or with your future self.

However, just starting out it might not be as necessary when you're hacking something together.

But, now you are aware of the general style if you should happen to be digging around through some package documentation.

Conclusion

Functions are an essential paradigm in any programming language-- in fact, there are languages that are purely functional. The key takeaways are that as a reader of a function, you'll probably be most interested in what arguments it takes and what values it returns, not so much the code is within the function.