What is an anonymous function in Go?
It is just a normal function without a name. Anonymous functions are also called lambda functions or function literals. They are commonly defined and are being passed around as small, self-contained code blocks.
Anonymous functions are often used in Go for simple tasks such as sorting or filtering a collection of data. They can also be used as closures to capture and manipulate variables in their surrounding environment.
Major use cases of the Anonymous function in Go
- Pass as value (as an argument to another function)
- Execute something concurrently
- Send value on a channel without blocking the caller
- Function returning function (closure)
Pass as value (as an argument to another function)
Let us understand with the classic example which most of us would already be familiar with as mentioned below:-
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
}
func main() {
students := []Student{
{Name: "Alice", Age: 21},
{Name: "Bob", Age: 19},
{Name: "Charlie", Age: 23},
{Name: "David", Age: 20},
}
// Custom comparison function for sorting by age
ageLess := func(i, j int) bool {
return students[i].Age < students[j].Age
}
// Use sort.Slice with the custom comparison function
sort.Slice(students, ageLess)
fmt.Println("Sorted by age:")
for _, student := range students {
fmt.Printf("Name: %s, Age: %d\n", student.Name, student.Age)
}
}
Explanation
Here, ageLess is an anonymous function since its value is a function without a name. It is being passed to sort.Slice (2nd argument). It is sort of lived since its scope is within the main function.
Note: Please install Go to run it locally on your system.
Execute something concurrently
Go anonymous function facilitates concurrent execution of independent tasks.
Let us extend the above example as below:-
After sorting we will find max age of all students and set its value in redis as below:-
package main
import (
"fmt"
"sort"
"sync"
"github.com/go-redis/redis/v8" // Import the Redis package
"golang.org/x/net/context" // Import context for Redis
)
type Student struct {
Name string
Age int
}
func main() {
students := []Student{
{Name: "Alice", Age: 21},
{Name: "Bob", Age: 19},
{Name: "Charlie", Age: 23},
{Name: "David", Age: 20},
}
// Custom comparison function for sorting by age
ageLess := func(i, j int) bool {
return students[i].Age < students[j].Age
}
// Use sort.Slice with the custom comparison function
sort.Slice(students, ageLess)
// Find the max age
maxAge := students[len(students)-1].Age
fmt.Println("Max Age:", maxAge)
// Update the max age in Redis concurrently
go updateMaxAgeInRedis(maxAge)
}
func updateMaxAgeInRedis(maxAge int) {
// Initialize a Redis client
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis server address
Password: "", // No password set
DB: 0, // Default DB
})
// Use a WaitGroup for concurrent goroutines
var wg sync.WaitGroup
wg.Add(1)
// Start a goroutine to update the max age in Redis
go func() {
defer wg.Done()
// Use a context for Redis operations
ctx := context.Background()
// Update the max age value in Redis
err := client.Set(ctx, "maxAge", maxAge, 0).Err()
if err != nil {
fmt.Println("Error updating max age in Redis:", err)
} else {
fmt.Println("Max age updated in Redis:", maxAge)
}
}()
wg.Wait() // Wait for the goroutine to finish
}
Explanation
We first sort students by age, then we assign the value of the last student’s age as maxAge and finally, we execute updateMaxAgeInRedis concurrently to set value in redis.
Send value on a channel without blocking the caller
Now suppose after calculating we also want to notify on Slack if maxAge is greater than 20.
We can extend the above example and see how this can be done.
package main
import (
"fmt"
"sort"
"sync"
"github.com/go-redis/redis/v8" // Import the Redis package
"golang.org/x/net/context" // Import context for Redis
)
type Student struct {
Name string
Age int
}
var maxAgeChan = make(chan int)
func main() {
students := []Student{
{Name: "Alice", Age: 21},
{Name: "Bob", Age: 19},
{Name: "Charlie", Age: 23},
{Name: "David", Age: 20},
}
// Custom comparison function for sorting by age
ageLess := func(i, j int) bool {
return students[i].Age < students[j].Age
}
// Use sort.Slice with the custom comparison function
sort.Slice(students, ageLess)
// Find the max age
maxAge := students[len(students)-1].Age
fmt.Println("Max Age:", maxAge)
// Update the max age in Redis concurrently
go updateMaxAgeInRedis(maxAge)
// send maxAge value on maxAgeChan concurrently
go func() {
maxAgeChan <- maxAge
}()
}
func updateMaxAgeInRedis(maxAge int) {
// Initialize a Redis client
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis server address
Password: "", // No password set
DB: 0, // Default DB
})
// Use a WaitGroup for concurrent goroutines
var wg sync.WaitGroup
wg.Add(1)
// Start a goroutine to update the max age in Redis
go func() {
defer wg.Done()
// Use a context for Redis operations
ctx := context.Background()
// Update the max age value in Redis
err := client.Set(ctx, "maxAge", maxAge, 0).Err()
if err != nil {
fmt.Println("Error updating max age in Redis:", err)
} else {
fmt.Println("Max age updated in Redis:", maxAge)
}
}()
wg.Wait() // Wait for the goroutine to finish
}
func notifyOnSlack() {
for maxAge := range maxAgeChan {
if maxAge <= 20 {
continue
}
// Initialize a Slack API client
api := slack.New("YOUR_SLACK_TOKEN")
// Set the message text
messageText := fmt.Sprintf("Max age is %d which is greater than 20!", maxAge)
// Post the message to the Slack channel
_, _, err := api.PostMessage("YOUR_CHANNEL_ID", slack.MsgOptionText(messageText, false))
if err != nil {
fmt.Println("Error posting message to Slack:", err)
} else {
fmt.Println("Message posted to Slack")
}
}
}
Explanation
We created a function(notifyOnSlack) that will continuously receive maxAge value on a channel and check if maxAge is greater than 20 then it will notify on a Slack channel.
Function returning function (closure)
Let us understand with the below example of a calculator.
func Add(n float64) func(float64) float64 {
return func(acc float64) float64 {
return acc + n
}
}
func Sub(n float64) func(float64) float64 {
return func(acc float64) float64 {
return acc - n
}
}
func Mul(n float64) func(float64) float64 {
return func(acc float64) float64 {
return acc * n
}
}
func Sqrt() func(float64) float64 {
return func(n float64) float64 {
return math.Sqrt(n)
}
}
func (c *Calculator) Do(op func(float64) float64) float64 {
c.acc = op(c.acc)
return c.acc
}
func main() {
var c Calculator
c.Do(Add(10)) // 10
c.Do(Add(20)) // 30
c.Do(Sub(5)) // 25
c.Do(Sqrt()) //5
}
Explanation
In the above example, Add, Sub, Mul, and Sqrt return a function having signature func(float64) float64. These returned functions are anonymous functions.
Also, in the case of Add, Sub, and Mul returned functions close n (surrounding value) and serve as closure too.
Conclusion
In conclusion, anonymous functions in Go stand as versatile tools empowering developers to create concise, flexible, and expressive code. These unnamed functions, encapsulating functionality without a declared identifier, provide a powerful means to adapt code dynamically, especially in scenarios requiring ad-hoc or short-lived functionality.
The flexibility afforded by anonymous functions in Go extends to various programming paradigms, enabling the creation of closures, callbacks, and concurrent operations. Their ability to capture and manipulate local variables within their lexical scope enhances code readability and maintains the integrity of the surrounding context.
Anonymous functions exemplify Go’s simplicity and efficiency, allowing developers to write clear and compact code without compromising functionality. Their usage facilitates modular design and cleaner code organization, promoting reusability and maintainability within applications.
Overall, anonymous functions in Go embody the language’s ethos of simplicity, promoting flexibility, readability, and code efficiency, thus serving as valuable assets for crafting robust and adaptable Go programs.