Functions as values

In Go, a function is not just something you call. It is also a value. That means you can store it in a variable, pass it as an argument, or return it from another function the same way you would with other values.

A function stored in a variable

Assign a function to a variable and use it through that variable:

func add(a, b int) int {
    return a + b
}

func main() {
    op := add
    fmt.Println(op(3, 4))   // 7
}

op now holds the add function. Its type is func(int, int) int, which is exactly the shape of add's signature: take two ints, return one int. Calling op(3, 4) runs add. There is no copy of the code; op refers to the same function.

The general shape of a function type is func(paramTypes) returnType. Any function with a matching signature can be assigned to a variable of that type.

Functions as arguments

Because functions are values, you can pass them as arguments to other functions. The parameter type spells out the expected signature:

func apply(op func(int, int) int, a, b int) int {
    return op(a, b)
}

func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

func main() {
    fmt.Println(apply(add, 3, 4))        // 7
    fmt.Println(apply(multiply, 3, 4))   // 12
}

apply does not care WHICH operation it runs. It accepts any function with the shape func(int, int) int and calls it with the two numbers. That is the payoff of functions-as-values: one generic helper works with many concrete operations, and you choose the operation at the call site.

The signature has to match:

func greet(name string) string {
    return "Hello, " + name
}

fmt.Println(apply(greet, 3, 4))   // compile error: cannot use greet (...) as func(int, int) int value in argument to apply

That is the other half of the rule. Functions are values, but their types still matter.

Anonymous functions

You do not have to give a function a name. You can define one on the spot where you need it:

fmt.Println(apply(
    func(a, b int) int {
        return a - b
    },
    10,
    4,
))   // 6

That inline func(a, b int) int { ... } is an anonymous function (also called a function literal). It has no name and exists only as a value at the call site. Use it when the operation is small enough that naming it would be more noise than help.

You can also assign an anonymous function to a local variable, which is the closest Go comes to declaring a function inside another function:

double := func(x int) int {
    return x * 2
}

fmt.Println(double(5))   // 10

double is a local variable holding a function; it exists only inside the enclosing function.

Functions returned from other functions

Functions can also be return values. This is where the next lesson (Closures) lives:

func adder() func(int) int {
    return func(x int) int {
        return x + 1
    }
}

adder returns a function of type func(int) int. You call the returned value the same way you would call any other function:

inc := adder()
fmt.Println(inc(5))   // 6

Naming a function type

When the same function signature appears in many places, you can give it a name with type:

type IntOp func(int, int) int

IntOp is now a named type whose underlying type is func(int, int) int. You can read that as "IntOp means a function that takes two ints and returns one int." Use it anywhere a function type would go:

type IntOp func(int, int) int

func apply(op IntOp, a, b int) int {
    return op(a, b)
}

func add(a, b int) int {
    return a + b
}

func multiply(a, b int) int {
    return a * b
}

func main() {
    fmt.Println(apply(add, 3, 4))        // 7
    fmt.Println(apply(multiply, 3, 4))   // 12
}

Two reasons to bother. First, the parameter reads op IntOp instead of op func(int, int) int, which is shorter when the signature keeps recurring. Second, the name documents intent: "this is an operation on two integers", not just "some function of this shape".

Name a function type only when both are true

The signature recurs in several places, and the name carries a specific meaning beyond the raw shape. IntOp is reasonable if your code keeps passing around arithmetic operations. A one-off helper does not need a named function type; inline func(int, int) int is often clearer.

Task

Write a function called transform that takes two arguments:

  • s, a string
  • fn, a function with the signature func(string) string

transform applies fn to s and returns the result.

Then in main, call transform three times and print each result on its own line:

  • transform("hello", strings.ToUpper) should print HELLO
  • transform("HELLO", strings.ToLower) should print hello
  • transform("hello", ...) with an anonymous function that appends "!" to its input. Should print hello!

Remember that strings.ToUpper and strings.ToLower live in the strings package, so add the import.

Expected output
HELLO
hello
hello!