First Class Functions
Welcome to tutorial no. 34 in Golang tutorial series.
What are first class functions?
A language that supports first class functions allows functions to be assigned to variables, passed as arguments to other functions and returned from other functions. Go has support for first class functions.
In this tutorial, we will discuss the syntax and various use cases of first class functions.
Anonymous functions
Let’s start with a simple example which assigns a function to a variable.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 a := func() {
9 fmt.Println("hello world first class function")
10 }
11 a()
12 fmt.Printf("%T", a)
13}
In the program above, we have assigned a function to the variable a
in line no. 8. This is the syntax for assigning a function to a variable. If you notice carefully, the function assigned to a
does not have a name. These kinds of functions are called anonymous functions since they do not have a name.
The only way to call this function is using the variable a
. We have done this in the next line. a()
calls the function and this prints hello world first class function
. In line no. 12 we print variable a
’s type . This will print func()
.
Running this program will print
hello world first class function
func()
It is also possible to call an anonymous function without assigning it to a variable. Let’s see how this is done in the following example.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 func() {
9 fmt.Println("hello world first class function")
10 }()
11}
In the program above, an anonymous function is defined in line no. 8 and immediately after the function definition, we call the function using ()
in line no. 10. This program will output,
hello world first class function
It is also possible to pass arguments to anonymous functions just like any other function.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 func(n string) {
9 fmt.Println("Welcome", n)
10 }("Gophers")
11}
In the program above, a string argument is passed to the anonymous function in line no. 10. Running this program will print,
Welcome Gophers
User defined function types
Just like we define our own struct types, it is possible to define our own function types.
type add func(a int, b int) int
The code snippet above creates a new function type add
which accepts two integer arguments and returns a integer. Now we can define variables of type add
.
Let’s write a program that defines a variable of type add
.
1package main
2
3import (
4 "fmt"
5)
6
7type add func(a int, b int) int
8
9func main() {
10 var a add = func(a int, b int) int {
11 return a + b
12 }
13 s := a(5, 6)
14 fmt.Println("Sum", s)
15}
In the program above, in line no.10, we define a variable a
of type add
and assign to it a function whose signature matches the type add
. We call the function in line no. 13 and assign the result to s
. This program will print,
Sum 11
Higher-order functions
The definition of Higher-order function from wiki is a function which does at least one of the following
- takes one or more functions as arguments
- returns a function as its result
Let’s look at some simple examples for the above two scenarios.
Passing functions as arguments to other functions
1package main
2
3import (
4 "fmt"
5)
6
7func simple(a func(a, b int) int) {
8 fmt.Println(a(60, 7))
9}
10
11func main() {
12 f := func(a, b int) int {
13 return a + b
14 }
15 simple(f)
16}
In the above example, in line no. 7 we define a function simple
which takes a function that accepts two int arguments and returns a int as a parameter. Inside the main function in line no. 12 we create an anonymous function f
whose signature matches the parameter of the function simple
. We call simple
and pass f
as an argument to it in the next line. This program prints 67
as output.
Returning functions from other functions
Now let’s rewrite the program above and return a function from the simple
function.
1package main
2
3import (
4 "fmt"
5)
6
7func simple() func(a, b int) int {
8 f := func(a, b int) int {
9 return a + b
10 }
11 return f
12}
13
14func main() {
15 s := simple()
16 fmt.Println(s(60, 7))
17}
In the program above, the simple function in line no 7 returns a function that takes two int
arguments and returns a int
argument.
This simple function is called from line no. 15. The return value from simple is assigned to s
. Now s
contains the function returned by simple
function. We call s
and pass it two int arguments in line no. 16. This program outputs 67
.
Closures
Closures are a special case of anonymous functions. Closures are anonymous functions that access the variables defined outside the body of the function.
An example will make things more clear.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 a := 5
9 func() {
10 fmt.Println("a =", a)
11 }()
12}
In the program above, the anonymous function accesses the variable a
which is present outside its body in line no. 10. Hence this anonymous function is a closure.
Every closure is bound to its own surrounding variable. Let’s understand what this means by using a simple example.
1package main
2
3import (
4 "fmt"
5)
6
7func appendStr() func(string) string {
8 t := "Hello"
9 c := func(b string) string {
10 t = t + " " + b
11 return t
12 }
13 return c
14}
15
16func main() {
17 a := appendStr()
18 b := appendStr()
19 fmt.Println(a("World"))
20 fmt.Println(b("Everyone"))
21
22 fmt.Println(a("Gopher"))
23 fmt.Println(b("!"))
24}
In the program above, the function appendStr
returns a closure. This closure is bound to the variable t
. Let’s understand what this means.
The variables a
and b
declared in line nos. 17, 18 are closures and they are bound to their own value of t
.
We first call a
with the parameter World
. Now the value of a
’s version of t
becomes Hello World
.
In line no. 20 we call b
with the parameter Everyone
. Since b
is bound to its own variable t
, b
’s version of t
has a initial value of Hello
again. Hence after this function call, the value of b
’s version of t becomes Hello Everyone
. The rest of the program is self-explanatory.
This program will print,
Hello World
Hello Everyone
Hello World Gopher
Hello Everyone !
Practical use of first class functions
Till now we have defined what first class functions are and we have seen a few contrived examples to learn how they work. Now let’s write a concrete program that shows the practical use of first class functions.
We will create a program that filters a slice of students based on some criteria. Let’s approach this step by step.
First lets define the student type.
1type student struct {
2 firstName string
3 lastName string
4 grade string
5 country string
6}
The next step is to write the filter
function. This function takes a slice of students and a function that determines whether a student matches the filtration criteria as parameters. We will understand better once we write this function. Let’s go ahead and do it.
1func filter(s []student, f func(student) bool) []student {
2 var r []student
3 for _, v := range s {
4 if f(v) == true {
5 r = append(r, v)
6 }
7 }
8 return r
9}
In the above function, the second parameter to filter
is a function which takes a student
as a parameter and returns a bool
. This function determines whether a particular student matches a criteria or not. We iterate through the student slice in line no. 3 and and we pass each student as a parameter to the function f
. If this returns true
, it means that that the student has passed the filter criteria and he is added to the slice r
. You might be a little confused about the real use of this function, but it will be clear once we complete the program. I have added the main function and have provided the full program below.
1package main
2
3import (
4 "fmt"
5)
6
7type student struct {
8 firstName string
9 lastName string
10 grade string
11 country string
12}
13
14func filter(s []student, f func(student) bool) []student {
15 var r []student
16 for _, v := range s {
17 if f(v) == true {
18 r = append(r, v)
19 }
20 }
21 return r
22}
23
24func main() {
25 s1 := student{
26 firstName: "Naveen",
27 lastName: "Ramanathan",
28 grade: "A",
29 country: "India",
30 }
31 s2 := student{
32 firstName: "Samuel",
33 lastName: "Johnson",
34 grade: "B",
35 country: "USA",
36 }
37 s := []student{s1, s2}
38 f := filter(s, func(s student) bool {
39 if s.grade == "B" {
40 return true
41 }
42 return false
43 })
44 fmt.Println(f)
45}
In the main function, we first create two students s1
and s2
and add them to slice s
. Now let’s say we want to find out all students who have grade B
. We have established this in the above program by passing a function which checks whether the student has grade B
and if so then returning true, as a parameter to the filter
function in line no. 38. The above program will print,
[{Samuel Johnson B USA}]
Let’s say we want to find all students from India. This can be done easily by changing the function parameter to the filter function. I have provided code that does this below,
1c := filter(s, func(s student) bool {
2 if s.country == "India" {
3 return true
4 }
5 return false
6})
7fmt.Println(c)
Please add this to the main function and check the output.
Let’s conclude this section by writing one more program. This program will perform the same operations on each element of a slice and return the result. For example, if we want to multiply all integers in a slice by 5 and return the output, it can be easily done using first class functions. These kinds of functions which operate on every element of a collection are called map
functions. I have provided the program below. It is self-explanatory.
1package main
2
3import (
4 "fmt"
5)
6
7func iMap(s []int, f func(int) int) []int {
8 var r []int
9 for _, v := range s {
10 r = append(r, f(v))
11 }
12 return r
13}
14func main() {
15 a := []int{5, 6, 7, 8, 9}
16 r := iMap(a, func(n int) int {
17 return n * 5
18 })
19 fmt.Println(r)
20}
The above program will print,
[25 30 35 40 45]
Here’s a quick recap of what we learned in this tutorial,
- What are first class functions?
- Anonymous functions
- User defined function types
- Higher-order functions
- Passing functions as arguments to other functions
- Returning functions from other functions
- Closures
- Practical use of first class functions
That’s about it for first class functions. I hope you liked this tutorial. Please leave your feedback and comments. Please consider sharing this tutorial on twitter and LinkedIn. Have a good day.
Next tutorial - Reflection
Previous tutorial - Panic and Recover