While working with a new language you will always try to find some similarities with languages that you already know. I’m experienced in JavaScript and Java and can conclude that Go is more similar to JS. Why is that so? Well, in my opinion, this is because of the supporting some functional paradigm features.
It is a basic entity of functional programming. From wiki:
the function return values are identical for identical arguments
Here is an example of a pure function:
func multiply(a, b int) int {
return a * b
}
According to the wiki, it means that:
a given entity (such as a function) supports all the operational properties inherent to other entities; properties such as being able to be assigned to a variable, passed around as a function argument, returned from a function, etc.
We can return a function:
package main
import "fmt"
func main() {
myFunc := makeCounter()
myFunc()
myFunc()
}
func makeCounter() func() {
counter := 0
return func() {
fmt.Println(counter)
counter++
}
}
What should you notice here is that makeCounter
function not only returns a new anonymous function but also got a variable that saves in anonymous function lexical closure, just like in JavaScript.
In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.
At the time this article is written, Go doesn’t support generics through Go version 1.18 which will include support of generic types should be released in March 2022
Go 1.18 is not yet released. These are work-in-progress release notes. Go 1.18 is expected to be released in March 2022.
Despite this fact you can also use some wrapper functions, for example, if you want to provide some data to the output while invoking certain functions like so:
func multiply(a, b int) int {
return a * b
}
func wrapWithLogs(fn func(a, b int) int) func(a,b int) int {
return func(a, b int) int {
start := time.Now()
r := fn(a, b)
duration := time.Since(start)
fmt.Println("function took ", duration)
return r
}
}
Of course, it will be much more effective using generics. So we need just to wait for a while until the new release will be published :)
If you want to get the name of the function dynamically you can also use:
runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
Wrapping opens a door to use some cool things like memoization
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
Like it said, if you have some heavy calculation - it is always better to cache already computed results somewhere and use them instead of doing the same calculation again.
func sum(a,b int) int {
return a + b
}
func memo(fn func(a, b int) int) func(a, b int) int {
cache := make(map[string]int)
return func(a, b int) int {
key := strconv.Itoa(a) + " " + strconv.Itoa(b)
v, ok := cache[key]
if !ok {
fmt.Println("calculating...")
cache[key] = fn(a, b)
v = cache[key]
}
return v
}
}
func main() {
mSum := memo(sum)
fmt.Println(mSum(2, 3))
fmt.Println(mSum(2, 3))
}
The result will be:
calculating... 5 5
Of course, you can avoid wrapper function and write memorization logic directly inside your function. But still you will use closure to store cache inside.
We can use recursion if we need to calculate something like factorial:
func funcFactorial(num int) int {
if num == 0 {
return 1
}
return num * funcFactorial(num-1)
}
But please be aware of using recursion whenever possible. The problem is that in most cases it is better to use some stack or queue instead of recursion. Usually, limits of memory lie further than the limits of the call stack.
Remember we discussed closure in the first example? Currying is about taking a parameter and returning it in a closure. For example, we got a function multiply:
func multiply(a, b int) int {
return a * b
}
We can call this function like multiply(2, 3)
. With currying, we can call this function in a way multiply(2)(3)
. To do that we need to rewrite our main function:
func multiply(a int) func(b int) int {
return func(b int) int {
return a * b
}
}
But rewriting existing function just to use currying is not a good practice. Using some wrapper function we can do it in a better way:
func multiply(a, b int) int {
return a * b
}
func curry(a int) func(b int) int {
return func(b int) int {
return multiply(a, b)
}
}
Go supports some features of the functional paradigm to make things easier. But don’t forget that it is structural language and there is no need to try to write Go code in Functional or OOP style. You will lose in both cases.