04. Using Functions

Arindam BaidyaArindam Baidya
10 min read
  • We will learn different king of functions in golang.

    1. High Order Functions

    2. Variadic Functions

    3. Anonymous Functions

Functions

  • Self contained units of code which carry out a certain job.

  • Help us dividing a program into small manageable, repeatable, and organizable chunks.

  • From a higher abstraction level, a function is just takes an input and returns an output.

Why to use functions?

  • Reusability: Define a function once, and call/use it multiple time.

  • Abstraction: To abstract the inner implementation, Ofcource we need to know the function name, working of the entire function, the returns values, and the arguments to create that function. But we need not to know how it works to just use it.

Function syntax

func functionName(params) returnType {
    // Function body
}

// func functionName(params) returnType :> Function signature

Example:

func addNumbers(a int, b int) int {
    // Function body
    return a + b
}
  • A function can return a value, or not even return anything.

  • When a return statement is reached in a function, the program returns to the code that invoked or called that function.

Calling a function

<function_name>(<argument(s)>)

  • Function name; argument(s)/input(s) that the function requires, separated by commas.

Example:

addNumbers(2, 3)

// Storing returns values by a function in a variable
sumOfNumbers := addNumbers(2, 3)

Naming Convention for functions

  • Must begin with a letter.

  • Can have any number of additional letters and symbols.

  • Cannot contain spaces.

  • Case-sensitive.

Parameters vs arguments

  • Function parameters are the names listed in the function’s definition.

    • We have input parameter(s) and a return parameter

    • Input parameter can be passed in two ways: 1. Call by value, and 2. Call by address

  • Function arguments are real values passed into the function.

func addNumbers(a int, b int) int { // Parameters
    sum := a + b
    return sum
}

func main() {
    sumOfNumbers := addNumbers(2, 3) // Arguments
    fmt.Println(sumOfNumbers)
}
func printGreeting(str string) {
    fmt.Println("Hey there, ", str)
}

func main() {
    printGreeting("Joe")
}

Function return-types

Returning single value

func addNumbers(a int, b int) string {
    sum := a + b
    return sum
}

func main() {
    sumOfNumbers := addNumbers(2, 3)
    fmt.Println(sumOfNumbers)
}

/*
This code will result in a compilation error because the addNumbers function 
is defined to return a string, but it attempts to return an int.
*/
  • Return value must match the function signature.

Returning multiple values

func operation(a int, b int) (int, int) {
    sum := a + b
    diff := a - b
    return sum, diff // Returing multiple values
}

func main() {
    sum, difference := operation(20, 10)
    fmt.Println(sum, difference)
}

// Output: 30 10
  • The statement containing the return values is also known as terminating statement. Here, return sum, diff is the terminating statement. As the function will terminate and return back to where it was called from the moment this statement is encountered.
func operation(a int, b int) (sum int, diff int) {
    sum = a + b // Already declared in the function signature
    diff = a - b  // sum, and diff
    return
}

func main() {
    sum, difference := operation(20, 10)
    fmt.Println(sum, " ", difference)
}

// Output: 30  10
  • Golang allows giving names to the return or result parameters of the functions in the function signature/definition.

Variadic functions

  • Function that accepts variable number of arguments.

  • It is possible to pass a varying number of arguments of the same type as referenced in the function signature.

  • To declare a variadic function, the type of the final parameter is preceded by an ellipsis “...”

  • Example - fmt.Println method.

Syntax

func <func_name>(param_1 type, param_2 type, param_3 ...type) <return_type>

It indicates that the function can called at any numbers of parameters of this type. There can be any number of parameters, but if we want to use variadic parameters, we’ll have to place it at the end.

func sumNumbers(numbers ...int) int

In the above example, the function signature accepts an arbitrary number of arguments of the type int. The function over here will accept zero or more integers, and within the function, the numbers variable will contain a slice of all the arguments.

func sumNumbers(str string, numbers ...int)

Here, we have two parameters, a string and multiple integers.

Note: How the variadic parameter is placed towards the end.

Example 1.

package main

import "fmt"

func sumNumbers(numbers ...int) int {
    sum := 0
    for _, value := range numbers {
        sum += value
    }
    return sum
}

func main() {
    fmt.Println(sumNumbers())              // Output: 0
    fmt.Println(sumNumbers(10))            // Output: 10
    fmt.Println(sumNumbers(10, 20))        // Output: 30
    fmt.Println(sumNumbers(10, 20, 30, 40, 50)) // Output: 150
}

Output:

0
10
30
150

Example 2.

package main

import "fmt"

func printDetails(student string, subjects ...string) {
    fmt.Printf("Hey %s, here are your subjects - ", student)
    for _, sub := range subjects {
        fmt.Printf("%s, ", sub)
    }
    fmt.Println()
}

func main() {
    printDetails("Joe", "Physics", "Biology")
}

// Output: Hey Joe, here are your subjects - Physics, Biology,

Blank identifier (‘_’)

  • The blank identifier is the single underscore operator. It is used to ignore the values that are returned by functions.

  • We have already used it in range function for looping.

Step 1: Using both return values

package main

import "fmt"

func f() (int, int) {
    return 42, 53
}

func main() {
    a, b := f()
    fmt.Println(a, b)
}

Output:

42 53

Step 2: Attempting to use only one variable

package main

import "fmt"

func f() (int, int) {
    return 42, 53
}

func main() {
    v := f()
    fmt.Println(v)
}

Error:

./prog.go:11:6: assignment mismatch: 1 variable but f returns 2 values

This error occurs because the function f returns two values, but only one variable v is used to capture them.

Step 3: Using a blank identifier to ignore the second return value

package main

import "fmt"

func f() (int, int) {
    return 42, 53
}

func main() {
    v, _ := f()
    fmt.Println(v)
}

Output:

42

In this step, the blank identifier _ is used to ignore the second return value, allowing the first value to be stored in v without causing an error.

Recursive function

  • Recursion is a concept where a function calls itself by direct or indirect means.

  • The function keeps calling itself until it reaches a base case

  • Used to solve a problem where the solution is dependent on the smaller instance of the same problem.

package main

import "fmt"

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

func main() {
    n := 5
    result := factorial(n)
    fmt.Println("Factorial of", n, "is:", result)
}

Output:

Factorial of 5 is: 120

Anonymous Functions

  • An anonymous function is a function that is declared without any named identifier to refer to it.

  • They can accepts inputs and return outputs, just as standard functions do.

  • They can be used for containing functionality that need not be named and possibly for short-term use.

Function inside function

package main

import "fmt"

func main() {
    x := func(l int, b int) int {
        return l * b
    }

    fmt.Printf("%T \n", x)
    fmt.Println(x(20, 30))
}

Output:

func(int, int) int 
600

Explanation:

In this code, an anonymous function is defined and assigned to the variable x. This function takes two integer parameters, l and b, and returns their product. The fmt.Printf statement prints the type of x, which is a function that takes two integers and returns an integer. The fmt.Println statement calls the function x with the arguments 20 and 30, and prints the result, which is 600. This demonstrates how anonymous functions can be used to encapsulate functionality without needing a named identifier.

package main

import "fmt"

func main() {
    x := func(l int, b int) int {
        return l * b
    }(20, 30)

    fmt.Printf("%T \n", x)
    fmt.Println(x)
}

Output:

int 
600

Explanation:

In this code, an anonymous function is immediately invoked with the arguments 20 and 30. The function takes two integer parameters, l and b, and returns their product. The result of this function call, 600, is assigned to the variable x. The fmt.Printf statement prints the type of x, which is int, since the function returns an integer. The fmt.Println statement then prints the value of x, which is 600. This demonstrates how an anonymous function can be used and immediately executed to produce a result.

package main

import (
    "fmt"
    "strconv"
)

var (
    square = func(n int) string {
        s := n * n
        return strconv.Itoa(s)
    }
)

func main() {
    result := square(5)
    fmt.Printf("The square of 5 is: %s, and its type is: %T\n", result, result)
}

Output:

The square of 5 is: 25, and its type is: string

In this example, the anonymous function is assigned to the variable square at the package level. This means it can be accessed from anywhere within the package. The function takes an integer n, calculates its square, converts the result to a string using strconv.Itoa, and returns it. The main function then calls square with the argument 5 and prints the result.

High order functions

  • Function that receives a function as an arguments or returns a function as output.

Why to use high order function?

  • Composition

    • Creating smaller functions that takes care or certain piece of logic.

    • Composing complex function by using different smaller functions.

  • Reduces bugs

  • Code gets easier to read and understand

Use case (Basic)

package main

import "fmt"

func calcArea(r float64) float64 {
    return 3.14 * r * r
}

func calcPerimeter(r float64) float64 {
    return 2 * 3.14 * r
}

func calcDia(r float64) float64 {
    return 2 * r
}

func main() {
    var query int
    var radius float64

    fmt.Print("Enter the radius of the circle: ")
    fmt.Scanf("%f", &radius)

    fmt.Printf("Enter - \n 1 - Area \n 2 - Perimeter \n 3 - Diameter:\n")
    fmt.Scanf("%d", &query)

    if query == 1 {
        fmt.Println("Result:", calcArea(radius))
    } else if query == 2 {
        fmt.Println("Result:", calcPerimeter(radius))
    } else if query == 3 {
        fmt.Println("Result:", calcDia(radius))
    } else {
        fmt.Println("Invalid query")
    }
}

For example, if the user inputs a radius of 5 and chooses 1 for the area, the output will be:

Enter the radius of the circle: 5
Enter - 
 1 - Area 
 2 - Perimeter 
 3 - Diameter:
1
Result: 78.5

If the user chooses 2 for the perimeter with the same radius, the output will be:

Enter the radius of the circle: 5
Enter - 
 1 - Area 
 2 - Perimeter 
 3 - Diameter:
2
Result: 31.400000000000002

And if the user chooses 3 for the diameter:

Enter the radius of the circle: 5
Enter - 
 1 - Area 
 2 - Perimeter 
 3 - Diameter:
3
Result: 10

If the user enters an invalid option, the output will be:

Invalid query
package main

import "fmt"

func calcArea(r float64) float64 {
    return 3.14 * r * r
}

func calcPerimeter(r float64) float64 {
    return 2 * 3.14 * r
}

func calcDia(r float64) float64 {
    return 2 * r
}

func printResult(radius float64, calcFunction func(r float64) float64) {
    result := calcFunction(radius)
    fmt.Println("Result:", result)
    fmt.Println("Thank You!..")
}

func getFunction(query int) func(r float64) float64 {
    queryToFunction := map[int]func(r float64) float64{
        1: calcArea,
        2: calcPerimeter,
        3: calcDia,
    }
    return queryToFunction[query]
}

func main() {
    var query int
    var radius float64

    fmt.Print("Enter the radius of the circle: ")
    fmt.Scanf("%f", &radius)

    fmt.Printf("Enter - \n 1 - Area \n 2 - Perimeter \n 3 - Diameter:\n")
    fmt.Scanf("%d", &query)

    if calcFunction := getFunction(query); calcFunction != nil {
        printResult(radius, calcFunction)
    } else {
        fmt.Println("Invalid query")
    }
}

Defer Statement

  • A defer statement delays the execution of a function until the surrounding function returns.

  • The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

package main

import "fmt"

func printName(str string) {
    fmt.Println(str)
}

func printRollNo(rno int) {
    fmt.Println(rno)
}

func printAddress(adr string) {
    fmt.Println(adr)
}

func main() {
    printName("Joe")
    defer printRollNo(24)
    printAddress("streat-45")
}

Output:

Joe
streat-45
24

Explanation:

  • The printName function is called first, printing "Joe".

  • The defer statement is used with printRollNo(24), which means this function call is deferred until the surrounding function (main) returns.

  • The printAddress function is called next, printing "streat-45".

  • After the main function completes its execution, the deferred printRollNo(24) is executed, printing 24.

The defer keyword ensures that printRollNo is executed last, even though it appears before printAddress in the code.

References

KodeKloud

0
Subscribe to my newsletter

Read articles from Arindam Baidya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arindam Baidya
Arindam Baidya