Golang functions: Difference between revisions

From wikinotes
(Created page with "<syntaxhighlight lang="go"> func greet(name string) string { return "Hello, " + name } </syntaxhighlight>")
 
 
(24 intermediate revisions by the same user not shown)
Line 1: Line 1:
Golang has first class functions, you can pass functions as parameters.
= Function Signatures =
<blockquote>
Function with arguments, return value
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
//        (param  type) (return-type)
func greet(name string) string {
func greet(name string) string {
     return "Hello, " + name
     return "Hello, " + name
}
}
</syntaxhighlight>
</syntaxhighlight>
Multiple return values
<syntaxhighlight lang="go">
// when multiple return values present, surround with brackets
func find(id int) (string, int) {
    // ...
}
// optionally, return values can be named
// (this has no implications for caller, it's simply documentation)
func find(id int) (name string, age int) {
    // 'values' is array ...
}
</syntaxhighlight>
Combine params with same type
<syntaxhighlight lang="go">
// params 'firstname', 'lastname' both have type 'string'
func register(firstname, lastname string) {
    // ...
}
</syntaxhighlight>
Param values vs pointers
<syntaxhighlight lang="go">
// if 'name' param changes in func, does not change outside
func register immutable(name string) { ... }
// if 'name' param changes in func, modifies value in memory
// (more efficient, does not copy data to new memory location)
func register_mutable(*name string) { ... }
</syntaxhighlight>
Variadic parameters (variable number of params)
<syntaxhighlight lang="go">
func sum(names ...string) {
    // 'names' is a slice of 'string's
    new_names = append([]int{alex}, names...) // prepend to names slice
}
</syntaxhighlight>
Returning pointers
<syntaxhighlight lang="go">
// even if variables are declared on the stack,
// you can safely return a pointer to them
// (as lomg as your return value is a pointer)
func DoThing() *int {
    var foo *int = &123
    return foo
}
</syntaxhighlight>
Optional parameters
<syntaxhighlight lang="go">
// all parameters are optional!
func DoThing(a string, b int) {
    fmt.Printf("'%s', '%s'", a, b)
    return
}
DoThing()  // "'', ''"
</syntaxhighlight>
Default parameter values
<syntaxhighlight lang="go">
// no syntactic sugar for defaults, but can handle inside function
func SayHello(name string) {
    if name == "" {
        name = "guest"
    }
    fmt.Printf("Hello %s", name)
}
</syntaxhighlight>
Functions as Parameters (TESTME!)
<syntaxhighlight lang="go">
func Visit(msg string, visitor func(string) string) string {
    return visitor(msg)
}
func Visitor(msg string) string {
    fmt.Println(msg)
}
// you can pass in a function
Visit("abc", Visitor)
// you can also define functions ad-hoc
Visit("abc", func(msg string) string {
})
</syntaxhighlight>
</blockquote><!-- Function Signatures -->
= Generics =
<blockquote>
Generic functions can accept params for a range of types.
<syntaxhighlight lang="go">
// this method accepts either an int/float for both 'a' and 'b' params
// (we define value for 'NUM', then reuse it)
func Sum[NUM int | float64](a NUM, b NUM) NUM {
    return a + b
}
</syntaxhighlight>
</blockquote><!-- Generics -->
= Deferred functions =
<blockquote>
Defer waits until a function is just about to exit (even on failure).<br>
This is similar to a try/finally block in other languages.<br>
Docs describe it as useful for releasing a mutex, for example.
<syntaxhighlight lang="go">
func WriteFile(filepath string) (success int) {
    fd, err := os.Open(filepath)
    defer fd.Close()  // run before function closes
    // ... other code ...
}
</syntaxhighlight>
* Deferred functions are executed in the order of '''last-in-first-out''' ('''LIFO''').
* Panics evaluate '''after''' deferred functions
Variables passed to a defferred function retain their value at the time the function call was made.
<syntaxhighlight lang="go">
func main() {
    a := "start"
    defer fmt.Println(a)
    b := "end"
}
// "start"  <-- 'a' at time of defer was 'start'
</syntaxhighlight>
You may want to handle '''errors from deferred functions'''. <br>
you can do this with a closure, but you'll need to return an array of errors.
<syntaxhighlight lang="go">
func DoThing() (result string, errs []error) {  // <-- errs !MUST! be declared here for deferred to be caught
    defer func() {
        err = pipe.Close()
        if err != nil {
            errs = append(errs, err)
        }
    }()
    return "success", errs
}
</syntaxhighlight>
</blockquote><!-- Deferred evaluation -->
= Function Overloading =
<blockquote>
Not supported in go.
</blockquote><!-- Function Overloading -->
= Anonymous Functions =
<blockquote>
You can define anynymous functions in go
<syntaxhighlight lang="go">
foo = func() { ... }
defer func() { ... }
go func() { ... }
</syntaxhighlight>
You can also use the <code>func</code> generic type to type these anonymous functions.
<syntaxhighlight lang="go">
var sum func(int, int) int
sum = func(a, b, int) int {
    return a + b
}
</syntaxhighlight>
</blockquote><!-- Anonymous Functions -->
= Recursion / Tail Call Optimization =
<blockquote>
{{ TODO |
Why wouldn't tailCount also continuously increase the stack? It feels like this would be even larger than regular recursion..
}}
Go does not do tail call optimization.
You can force this optimization by executing your recursive function call in a second function.<br>
See https://medium.com/@meeusdylan/tail-recursion-in-go-fb5cf69a0f26
<syntaxhighlight lang="go">
func tailCount(i int) {
    return count(i)
}
func count(i int) {
    if i == 20 {
        return i
    }
    return tailCount(i)
}
</syntaxhighlight>
</blockquote><!-- Recursion / Tail Call Optimizatio -->

Latest revision as of 03:24, 17 July 2022

Golang has first class functions, you can pass functions as parameters.

Function Signatures

Function with arguments, return value

//        (param  type) (return-type)
func greet(name string) string {
    return "Hello, " + name
}

Multiple return values

// when multiple return values present, surround with brackets
func find(id int) (string, int) {
    // ...
}

// optionally, return values can be named
// (this has no implications for caller, it's simply documentation)
func find(id int) (name string, age int) {
    // 'values' is array ...
}

Combine params with same type

// params 'firstname', 'lastname' both have type 'string'
func register(firstname, lastname string) {
    // ...
}

Param values vs pointers

// if 'name' param changes in func, does not change outside
func register immutable(name string) { ... }

// if 'name' param changes in func, modifies value in memory
// (more efficient, does not copy data to new memory location)
func register_mutable(*name string) { ... }

Variadic parameters (variable number of params)

func sum(names ...string) {
    // 'names' is a slice of 'string's
    new_names = append([]int{alex}, names...) // prepend to names slice
}

Returning pointers

// even if variables are declared on the stack,
// you can safely return a pointer to them
// (as lomg as your return value is a pointer)
func DoThing() *int {
    var foo *int = &123
    return foo
}

Optional parameters

// all parameters are optional!
func DoThing(a string, b int) {
    fmt.Printf("'%s', '%s'", a, b)
    return
}

DoThing()  // "'', ''"

Default parameter values

// no syntactic sugar for defaults, but can handle inside function
func SayHello(name string) {
    if name == "" {
        name = "guest"
    }
    fmt.Printf("Hello %s", name)
}

Functions as Parameters (TESTME!)

func Visit(msg string, visitor func(string) string) string {
    return visitor(msg)
}

func Visitor(msg string) string {
    fmt.Println(msg)
}

// you can pass in a function
Visit("abc", Visitor)

// you can also define functions ad-hoc
Visit("abc", func(msg string) string {
})

Generics

Generic functions can accept params for a range of types.

// this method accepts either an int/float for both 'a' and 'b' params
// (we define value for 'NUM', then reuse it)
func Sum[NUM int | float64](a NUM, b NUM) NUM {
    return a + b
}

Deferred functions

Defer waits until a function is just about to exit (even on failure).
This is similar to a try/finally block in other languages.
Docs describe it as useful for releasing a mutex, for example.

func WriteFile(filepath string) (success int) {
    fd, err := os.Open(filepath)
    defer fd.Close()  // run before function closes

    // ... other code ...
}
  • Deferred functions are executed in the order of last-in-first-out (LIFO).
  • Panics evaluate after deferred functions

Variables passed to a defferred function retain their value at the time the function call was made.

func main() {
    a := "start"
    defer fmt.Println(a)
    b := "end"
}
// "start"   <-- 'a' at time of defer was 'start'

You may want to handle errors from deferred functions.
you can do this with a closure, but you'll need to return an array of errors.

func DoThing() (result string, errs []error) {  // <-- errs !MUST! be declared here for deferred to be caught
    defer func() {
        err = pipe.Close()
        if err != nil {
            errs = append(errs, err)
        }
    }()

    return "success", errs
}

Function Overloading

Not supported in go.

Anonymous Functions

You can define anynymous functions in go

foo = func() { ... }
defer func() { ... }
go func() { ... }

You can also use the func generic type to type these anonymous functions.

var sum func(int, int) int
sum = func(a, b, int) int {
    return a + b
}

Recursion / Tail Call Optimization

TODO:

Why wouldn't tailCount also continuously increase the stack? It feels like this would be even larger than regular recursion..

Go does not do tail call optimization.

You can force this optimization by executing your recursive function call in a second function.
See https://medium.com/@meeusdylan/tail-recursion-in-go-fb5cf69a0f26

func tailCount(i int) {
    return count(i)
}

func count(i int) {
    if i == 20 {
        return i
    }
    return tailCount(i)
}