First Class Functions

First-Class Functions in Go

A language that supports first-class functions allows functions to be:

  • Assigned to variables.
  • Passed as arguments to other functions.
  • Returned from other functions.
Anonymous Functions

Anonymous functions are functions without a name, which can be assigned to variables or invoked immediately.

Example of Assigning a Function to a Variable:

package main

import (
	"fmt"
)

func main() {
	greet := func() {
		fmt.Println("Hello from an anonymous function!")
	}
	greet()
	fmt.Printf("Type of greet: %T", greet)
}

Output:

Hello from an anonymous function!
func()

Here, greet holds an anonymous function that is invoked using greet().

Immediately Invoking an Anonymous Function

package main

import (
	"fmt"
)

func main() {
	func() {
		fmt.Println("Hello, Go developers!")
	}()
}

Output:

Hello, Go developers!

Anonymous Function with Arguments

package main

import (
	"fmt"
)

func main() {
	func(name string) {
		fmt.Println("Welcome,", name)
	}("Developers")
}

Output:

Welcome, Developers
User-Defined Function Types

You can define custom types for functions, just like structs.

type mathOp func(x, y int) int

The above creates a new type mathOp for functions that take two int arguments and return an int.

Example:

package main

import (
	"fmt"
)

type mathOp func(x, y int) int

func main() {
	add := func(x, y int) int {
		return x + y
	}
	var operation mathOp = add
	fmt.Println("Sum:", operation(3, 7))
}

Output:

Sum: 10
Higher-Order Functions

A higher-order function:

  • Accepts other functions as arguments.
  • Returns a function as its result.

Let’s look at some simple examples for the above two scenarios.

package main

import (
	"fmt"
)

func compute(op func(x, y int) int) {
	fmt.Println("Result:", op(10, 5))
}

func main() {
	multiply := func(x, y int) int {
		return x * y
	}
	compute(multiply)
}

Output:

Result: 50
Returning Functions from Functions
package main

import (
	"fmt"
)

func generateMultiplier(factor int) func(int) int {
	return func(x int) int {
		return x * factor
	}
}

func main() {
	double := generateMultiplier(2)
	fmt.Println("Double of 8:", double(8))
}

Output:

Double of 8: 16
Closures

A closure is an anonymous function that captures and uses variables from its surrounding scope.

Example:

package main

import (
	"fmt"
)

func main() {
	message := "Hello"
	func() {
		fmt.Println(message)
	}()
}

Output:

Hello
Independent Closures
package main

import (
	"fmt"
)

func counter() func() int {
	value := 0
	return func() int {
		value++
		return value
	}
}

func main() {
	c1 := counter()
	c2 := counter()

	fmt.Println(c1()) // 1
	fmt.Println(c1()) // 2
	fmt.Println(c2()) // 1
}

Output:

1
2
1
Practical Examples of First-Class Functions

Filtering a Slice

package main

import (
	"fmt"
)

type student struct {
	name   string
	grade  string
	country string
}

func filter(students []student, criteria func(student) bool) []student {
	var result []student
	for _, s := range students {
		if criteria(s) {
			result = append(result, s)
		}
	}
	return result
}

func main() {
	students := []student{
		{name: "Alice", grade: "A", country: "USA"},
		{name: "Bob", grade: "B", country: "India"},
	}

	byGradeB := filter(students, func(s student) bool {
		return s.grade == "B"
	})
	fmt.Println("Students with grade B:", byGradeB)
}

Output:

Students with grade B: [{Bob B India}]

Map Function

package main

import (
	"fmt"
)

func mapInts(numbers []int, operation func(int) int) []int {
	var result []int
	for _, n := range numbers {
		result = append(result, operation(n))
	}
	return result
}

func main() {
	numbers := []int{2, 3, 4}
	squared := mapInts(numbers, func(n int) int {
		return n * n
	})
	fmt.Println("Squared values:", squared)
}

Output:

Squared values: [4 9 16]