Fun with Go Functions

Fun with Go Functions

Go programming language has very powerful features for functions. Let's see what all fun we can have with functions!

1. In Go, it is not required for a function to accept or return a value.

We mainly use function to define a reusable code that can be called by name. Here is how we declare a simple function in Go.

func simpleFunction() {
    fmt.Println("Hello from function")
}

Now we can call simpleFunction from our main function. If you see, we are not passing any value to this function, nor this function is returning value.

2. Of course, function can accept and return values as well

We can specify function to accept input in form of function parameter and return output using return value.

func simpleFunction(num int) string {
    fmt.Println(num)
    return "Message printed!"

}

We are specifying "num" as function parameter and it returns a string.

3. Interestingly we can define name for the value that function returns!

func simpleFunction(num int) (result string) {
    fmt.Println(num)
    return "Message printed!"

}

4. And if we have defined name for return type, empty return statement will return the value assigned to that return variable name

func simpleFunction(num int) (result string) {
    fmt.Println(num)
    result = "Message printed!"
    return

}

we are assigning value to result. empty return will return this result.

5. We can also return multiple values!

You may not have seen this in other languages, but Go function can return multiple values

func simpleFunction(num int) (result string, doubledVal int) {
    fmt.Println(num)
    doubledVal = num * 2
    result = "Message printed!"
    return

}

We are returning result and doubledVal from this function. This is widely used in returning values and errors from a function.

6. If we return multiple values, we have to accept them all!

When calling a function which returns multiple values, we need to provide multiple variables which will accept those returned values like this:

func main() {
    value := 10
    result, newVal := simpleFunction(value)
    fmt.Println(result)
    fmt.Println(newVal)
}

7. If we don't want to accept all values returned, we need to tell it to Go!

If function is returning multiple values and we only need to work with one of the value, we need to say it using "_"

func main() {
    value := 10
    _, newVal := simpleFunction(value)
    fmt.Println(newVal)
}

we are ignoring first value returned, we can also ignore second value returned:

func main() {
    value := 10
    result, _ := simpleFunction(value)
    fmt.Println(result)
}

8. If we are passing parameters of same type, we could just define their type just once at the end

func sum(first, second int) int {
    return first + second
}

9. Anonymous Function

Go supports anonymous function. We could declare them(but not at package level) as :

func main() {
    func() {
        fmt.Println("Hello from anonymous!")
    }()
}

Anonymous functions are used in simpler codes like small go-routines or similar to callback functions:

func main() {
    data := "Hello World!"
    strings.Map(func(r rune) rune {
        fmt.Printf("%c\n", r)
        return r
    }, data)
}

we are passing anonymous function to Map function to handle each rune

10. A function can be made to accept any type of data!

Yes, you can define a function in such a way that it can accept data of any type using empty interface.

func simpleFunc(data interface{}) {
    fmt.Printf("Value is => %v, and Type is => %T \n", data, data)
}

since every Go value satisfies empty interface, simpleFunc can accept any value:

func main() {
    stringData := "Hello World!"
    intData := 10
    floatData:= 3.14

    simpleFunc(stringData)
    simpleFunc(intData)
    simpleFunc(floatData)
}

11. Similarly a function can be made to return any value

This may not be useful in practice, but it is possible!

func simpleFunc() interface{} {
    s1 := rand.NewSource(time.Now().UnixNano())
    r1 := rand.New(s1)
    num := r1.Intn(100)
    if num > 50 {
        return "Hello World"
    }
    return 3.14
}

We are returning either string or float64 in this case.

12. Variadic function

We can define a Go function to be variadic. This means it can accept any number of arguments of a particular type.To declare a variadic function, the type of the final parameter is preceded by an ellipsis, “...”, which indicates that the function may be called with any number of arguments of this type

func sum(vals ...int) (total int) {
    for _, val := range vals {
        total += val
    }
    return
}

we can call this sum functions by passing any number of integers:

func main() {
    total := sum(1, 2, 3, 4, 5)
    fmt.Println(total)
}

13. Combining empty interface and variadic function

We could define a function which accepts any number of arguments of any types! Yes, this is not JavaScript, this is Go

func simpleFunc(...interface{}) {

}

In fact, fmt.Println is implemented in same way!

14. We can assign a function to a variable, similar to any other value assignment

func main() {
    var hello = func() {
        fmt.Println("I am a function assigned to a variable!")
    }

    hello()
}

15. We can also return a function from another function!

A Go function can also return function when called. We can store this function and execute later...

func simpleFunc() func() {
    return func() {
        fmt.Println("I am a function returned from another function!")
    }
}

If we call this function, it will return a function. We can execute this returned function.

func main() {
    funcVar:= simpleFunc()

    funcVar()
}

16. A function can also accept another function

A function can also accept another function as its parameter.

func simpleFunc(f func()) {
    fmt.Println("Now executing accepted function...")
    f()
}

We can now pass any function to this simpleFunc like:

func main() {
    funcVar := func() {
        fmt.Println("I will be passed!")
    }
    simpleFunc(funcVar)
}

Passing and returning of functions is useful in building Higher order function (FP)

17. Within function, we can defer execution

Go has special keyword to defer operations just before function returns.

func simpleFunc() string {
    hello := "world"
    defer fmt.Println("I will be called before return!")
    fmt.Println("Now returing...")
    return hello
}

defer statement will be executed last before function return. This is generally used to perform cleanup. We can safely define usage and cleanup or resource close in source code using defer.

18. If there are multiple defers, they will be executed in reverse order.

This will call "Defer 2" first, then "Defer 1" will be called.

func simpleFunc() string {
    hello := "world"
    defer fmt.Println("Defer 1")
    defer fmt.Println("Defer 2")
    fmt.Println("Now returing...")
    return hello
}

19. Even if panic, defers will be called

If there is panic during runtime, Go will make sure that defers are executed.

func simpleFunc() string {
    hello := "world"
    defer fmt.Println("Defer 1")
    defer fmt.Println("Defer 2")
    panic("panic")
    return hello
}

Here also, Defer 2 and Defer 1 will be executed

20. Function is unit for concurrency

In Go, we can use concurrency with function as unit of concurrency. Any function can be executed concurrently using go keyword:

func simpleFunc() {
    fmt.Println("Hello Concurrency")
}

func main() {
    go simpleFunc()
}

Calling simpleFunc with go caused a new go-routine to start execution of this function! Concurrency could not get any simpler.

21. Functions have closure

When we return a function "A" from another function "B", that returned function "A" gets a closure over lexical scope of "B". What this means is even if "B" has completed execution, "A" still remembers lexical scope variable and values on "B"

func nextSquare() func() int {
    i := 0
    return func() int {
        i++
        return i * i
    }
}

nextSquare is higher order function which returns another function. This returned function can be called to get square of a number. What is interesting is, this returned function is able to remember variable i and its value when it is declared. Calling this function will increment i by 1 and return its square

func main() {
    f := nextSquare()
    for i := 0; i < 5; i++ {
        fmt.Printf("%v\t", f())
    }
}
// output: 1       4       9       16      25

22. Functions can become methods

A method in Go is a function that is tied to a type. We define methods similar to functions, but with additional data of type to which this function is tied.

type myType struct {
}

func (t myType) simpleFunc() {
    fmt.Println("I am a method now!")
}

(t myType) declaration above function is called method receiver. Now simpleFunc is a method attached to myType . We can use this as :

func main() {
    t := myType{}
    t.simpleFunc()
}

Did you had fun? Share with your fellow gopher friends! Happy Coding. Image credits : github.com/ashleymcnamara/gophers