Higher-Order Functions in Go

Rajiv Ranjan Singh

Rajiv Ranjan Singh / May 23, 2023

2 min read––– views

In software engineering, we frequently encounter many technical challenges. So, in my past experience in software engineering, I was working on a problem statement where I had to write repetitive logging code in every method. The codebase had numerous methods, and I wanted to log the entry and exit points of each method.

Writing repetitive logging code in every method can be cumbersome and error-prone. Therefore, I was looking for a way to enhance code reusability.

In each method, I had the following two lines:

logs.Trace("Entry: <method name>")
defer logs.Trace("Exit: <method name>")

This code utilizes a logging mechanism to trace the entry and exit points of methods. The logs.Trace function is used to log these events. The defer keyword ensures that the second line, which logs the method exit, is executed after the method completes.

To tackle this problem, I explored the concept of Higher-Order Functions. Higher-order functions are functions that can take other functions as parameters or return functions as results. They provide a powerful mechanism to encapsulate reusable behavior and enhance code modularity.

Here's an example implementation using a higher-order function:

package main

import (
    "fmt"
)

func trace(functionName string) func() {
    fmt.Printf("Entering '%s'\\n", functionName)

    return func() {
        fmt.Printf("Leaving '%s'\\n", functionName)
    }
}

func foo() {
    defer trace("foo")()
    fmt.Println("Executing foo")
}

func main() {
    foo()
}

In this implementation:

Step 1

  • The trace function is responsible for logging the entry and exit of a function.
  • When defer trace("foo")() is called in the foo function, the trace function is immediately invoked and logs the entry of foo.
  • The trace function returns a function that will log the exit of the function.
  • The returned function is the one actually deferred by defer trace("foo")(), and it will be executed when foo returns, logging the exit message.

By utilizing higher-order functions, I achieve cleaner and more maintainable code. Additionally, I can reuse the trace function in other methods as well.