The Zoo of Go Functions

An overview of anonymous, higher-order, closures, concurrent, deferred, and other kinds of Golang funcs.

Inanc Gumus
Learn Go Programming
9 min readNov 9, 2017

--

This post is a summary for the different kind of funcs in Go. I’ll go into more detail in the upcoming posts because they deserve more. This is just a start.

Named Funcs

A named func has a name and declared at the package-level — outside of the body of another func.

👉 I already have explained them fully in my another post, here.

This is a named func: Len func takes a string and returns and int

Variadic funcs

Variadic funcs can accept an optional number of input parameters.

👉 To learn more about them check out my other post, here.

Methods

When you attach a func to a type the func becomes a method of that type. So, it can be called through that type. Go will pass the type (the receiver) to the method when it’s called.

Example

Create a new counter type and attach a method to it:

type Count intfunc (c Count) Incr() int {
c = c + 1
return int(c)
}

The method above is similar to this func:

func Incr(c Count) int
Not exactly true but you can think of the methods as above

Value receiver

The value of the Count instance is copied and passed to the method when it’s called.

var c Count; c.Incr(); c.Incr()// output: 1 1

It doesn’t increase because “c” is a value-receiver.

Pointer receiver

To increase the counter’s value you need to attach the Incr func to the Count pointer type*Count.

func (c *Count) Incr() int {
*c = *c + 1
return int(*c)
}
var c Countc.Incr(); c.Incr()// output: 1 2

There are more examples from my previous posts: here and here.

Interface methods

Let’s recreate the above program using the interface methods. Let’s create a new interface named as Counter:

type Counter interface {
Incr() int
}

onApiHit func below can use any type which has an Incr() int method:

func onApiHit(c Counter) {
c.Incr()
}

Just use our dummy counter for now — you can use a real api counter as well:

dummyCounter := Count(0)onApiHit(&dummyCounter)// dummyCounter = 1

Because the Count type has the Incr() int method on its method list, onApiHit func can use it to increase the counter — I passed a pointer of dummyCounter to onApiHit, otherwise, it wouldn’t increase the counter.

The difference between the interface methods and the ordinary methods is that the interfaces are much more flexible and loosely-coupled. You can switch to different implementations across packages without changing any code inside onApiHit etc.

First-class funcs

First-class means that funcs are value objects just like any other values which can be stored and passed around.

Funcs can be used with other types as a value and vice-versa

Example:

The sample program here processes a sequence of numbers by using a slice of Crunchers as an input param value to a func named “crunch”.

Declare a new “user-defined func type” which takes an int and returns an int.

This means that any code that uses this type accepts a func with this exact signature:

type Cruncher func(int) int

Declare a few cruncher funcs:

func mul(n int) int {
return n * 2
}
func add(n int) int {
return n + 100
}
func sub(n int) int {
return n - 1
}

Crunch func processes a series of ints using a variadic Cruncher funcs:

func crunch(nums []int, a ...Cruncher) (rnums []int) {  // create an identical slice
rnums = append(rnums, nums...)
for _, f := range a {
for i, n := range rnums {
rnums[i] = f(n)
}
}
return
}

Declare an int slice with some numbers and process them:

nums := []int{1, 2, 3, 4, 5}crunch(nums, mul, add, sub)

Output:

[101 103 105 107 109]

Anonymous funcs

A noname func is an anonymous func and it’s declared inline using a function literal. It becomes more useful when it’s used as a closure, higher-order func, deferred func, etc.

Signature

A named func:

func Bang(energy int) time.Duration

An anonymous func:

func(energy int) time.Duration

They both have the same signature, so they can be used interchangeably:

func(int) time.Duration

Example

Let’s recreate the cruncher program from the First-Class Funcs section above using the anonymous funcs. Declare the crunchers as anonymous funcs inside the main func.

func main() {  crunch(nums,
func(n int) int {
return n * 2
},
func(n int) int {
return n + 100
},
func(n int) int {
return n - 1
})
}

This works because, crunch func only expects the Cruncher func type, it doesn’t care that they’re named or anonymous funcs.

To increase the readability you can also assign them to variables before passing to crunch func:

mul := func(n int) int {
return n * 2
}
add := func(n int) int {
return n + 100
}
sub := func(n int) int {
return n - 1
}
crunch(nums, mul, add, sub)

Higher-Order funcs

A higher order func may take one or more funcs or it may return one or more funcs. Basically, it uses other funcs to do its work.

Split func in the closures section below is a higher-order func. It returns a tokenizer func type as a result.

Closures

A closure can remember all the surrounding values where it’s defined. One of the benefits of a closure is that it can operate on the captured environment as long as you want — beware the leaks!

Example

Declare a new func type that returns the next word in a split string:

type tokenizer func() (token string, ok bool)

Split func below is an higher-order func which splits a string by a separator and returns a closure which enables to walk over the words of the split string. The returned closure can use the surrounding variables: “tokens” and “last”.

Let’s try:

const sentence = "The quick brown fox jumps over the lazy dog"iter := split(sentence, " ")for token, ok := iter(); ok; token, ok = iter() { 
fmt.Println(token)
}
  • Here, I use the split func to split the sentence into words and then get a new iterator func as a result value and put it into the iter variable.
  • Then, I start a loop which terminates only when the iter func returns false.
  • Each call to the iter func returns the next word.

The result:

The
quick
brown
fox
jumps
over
the
lazy
dog
Again, more explanations are inside.

Defer funcs

A deferred func is only executed after its parent func returns. Multiple defers can be used as well, they run as a stack, one by one.

👉 To learn more about them check out my post about Go defers, here.

Concurrent funcs

go func() runs the passed func concurrently with the other goroutines.

A goroutine is a lighter thread mechanism which allows you to structure concurrent programs efficiently. The main func executes in the main-goroutine.

Example

Here, “start” anonymous func becomes a concurrent func that doesn’t block its parent func’s execution when called with “go” keyword:

start := func() {
time.Sleep(2 * time.Second)
fmt.Println("concurrent func: ends")
}
go start()fmt.Println("main: continues...")
time.Sleep(5 * time.Second)
fmt.Println("main: ends")

Output:

main: continues...
concurrent func: ends

main: ends

Main func would terminate without waiting for the concurrent func to finish if there were no sleep call in the main func:

main: continues...
main: ends

Other Types

Recursive funcs

You can use recursive funcs as in any other langs, there is no real practical difference in Go. However, you must not forget that each call creates a new call stack. But, in Go, stacks are dynamic, they can shrink and grow depending on the needs of a func. If you can solve the problem at hand without a recursion prefer that instead.

Black hole funcs

A black hole func can be defined multiple times and they can’t be called in the usual ways. They’re sometimes useful to test a parser: see this.

func _() {}
func _() {}

Inlined funcs

Go linker places a func into an executable to be able to call it later at the run-time. Sometimes calling a func is an expensive operation compared to executing the code directly. So, the compiler injects func’s body into the caller. To learn more about them: Read this and this and this (by: Felipe) and this.

External funcs

If you omit the func’s body and only declare its signature, the linker will try to find it in an external func that may have written elsewhere. As an example, Atan func here just declared with a signature and then implemented in here.

Alright, that’s all for now. Thank you for reading so far.

Let’s stay in touch:

--

--