High order functions and Closures

High order functions and Closures

Learn closures and high order functions using Go and Julia

ยท

6 min read

High order function

In functional programming, a higher-order function is a function that takes other functions as its arguments or returns a function as its result.

Here are some examples of higher-order functions:

  • Map: A function that takes a function and an iterable as arguments and returns a new iterable with the function applied to each element of the original iterable.

  • Filter: A function that takes a function and an iterable as arguments and returns a new iterable with only the elements of the original iterable that satisfy the function.

  • Reduce: A function that takes two functions and an iterable as arguments and returns a single value by repeatedly applying the two functions to the elements of the iterable.

  • Fold: A function that takes two functions and an initial value as arguments and returns a single value by repeatedly applying the two functions to the previous value and the next element of the iterable.

  • Apply: A function that takes a function and an argument as arguments and returns the result of applying the function to the argument.

Higher-order functions are a powerful tool for functional programming. They can be used to write concise, elegant, and reusable code.

Local context

Local context is a concept that allows you to define variables that are only visible within a specific block of code. This can be useful for creating runtime reusable runtime functions.

One way to use local context is to create a higher-order function. A higher-order function is a function that takes other functions as its arguments or returns a function as its result.

For example, the following code defines a higher-order function called my_closure that takes a function as an argument and returns a function that calls the argument with the current value of the variable x:

func my_closure(f func()) func() {
    return f(x)
}

This function can be used to create different local contexts by calling it withing a function that defines the variables you want to make visible. For example, the following code defines a local context with the variables x and y:

func main() {
    // Define a function that increments its argument
    func increment(x int) {
        x++
    }

    // Create a local context with the variables x and y
    closure := my_closure(increment)

    // Call the closure
    closure()

    // Print the value of x
    fmt.Println(x)
}

This code will print 1, as the variable x is defined in the local context created by the my_closure function.

Julia vs Go

In both Go and Julia, the local context of a variable is determined by the function in which it is defined. However, there are some key differences in how variables are bound to their local context in each language.

In Go, variables are bound statically. This means that the value of a variable is determined at compile time. In Julia, variables are bound dynamically.

This difference in binding can lead to different programming patterns in Go and Julia. For example, in Go, it is common to use global variables to share data between functions. In Julia, it is not common to use global variables to share data between functions.

Closure in Go

A closure is a special function in Go that can refer to variables that are not explicitly passed to it. This is because closures are created by functions, and they inherit the scope of the function that created them.

For example, the following code defines a closure that can refer to the variable x:

func main() {
    x := 1

    func my_closure() int {
        return x
    }

    // Create a closure that refers to the variable x
    closure := my_closure

    // Call the closure
    fmt.Println(closure())
}

This code will print 1, as the closure is able to refer to the variable x because it is defined in the scope of the function that created the closure.

Closures can be used to create functions that can be reused multiple times. For example, the following code defines a function that calculates the factorial of a number:

func factorial(n int) int {
    if n == 0 {
        return 1
    }

    // Create a closure that refers to the variable n
    closure := func() int {
        return n
    }

    // Return the factorial of n
    return closure() * factorial(n-1)
}

This function can be used to calculate the factorial of any number. For example, the following code calculates the factorial of 5:

fmt.Println(factorial(5))

This code will print 120, as the factorial of 5 is 120.

Closures can also be used to create functions that can be passed as arguments to other functions. For example, the following code defines a function that takes a function as an argument and calls that function:

func call(f func()) {
    f()
}

func main() {
    // Define a function that increments its argument
    func increment(x int) {
        x++
    }

    // Call the call function with the increment function as an argument
    call(increment)
}

This code will print 2, as the increment function will be called with the value 1 as an argument.

Closure in Julia

In Julia, a closure is a function object that can capture and retain the values of its enclosing environment, even after execution has left that environment. In other words, a closure is a function that "remembers" its creation context, including any local variables and arguments that were in scope at the time the closure was defined.

Closures can be created using the function keyword, just like any other function in Julia. However, closures differ from regular functions in how they store and access their environment values.

Here's an example of a closure that captures the value of a local variable:

function make_counter()
    count = 0
    increment() = begin
        count += 1
        println("Count is now $count")
    end
    return increment
end

# Create a new counter closure
counter = make_counter()

# Call the closure multiple times to increment the count
counter()
counter()
counter()

In this example, the make_counter function returns a closure that increments and displays a local variable count every time it is called. The increment function is defined inside the make_counter function and captures the local variable count in its closure.

When we call make_counter, it returns the increment closure which we assign to the variable counter. We can then call counter multiple times to increment the count and display its value.

Notice that the count variable is not accessible outside of the closure, and is not even visible in the global scope. This is because count exists only in the local scope of the make_counter function, but is captured and remembered by the increment closure.


Conclusion

In conclusion, closures are a powerful feature in Julia that allow functions to capture and retain the values of their enclosing environment. Closures can be used to create flexible and reusable code that adapts to a wide range of use cases.

Note: HashNode do not suport Julia color syntax


Do you find this article useful? Comment below ๐Ÿ™๐Ÿผ

Did you find this article valuable?

Support Software Engineering by becoming a sponsor. Any amount is appreciated!

ย