Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting function calls in GO

Tags:

go

Let us say we want to implement following computation:

outval / err = f3(f3(f1(inval))

where each of f1, f2, f3 can fail with an error at that time we stop the computation and set err to error returned by the failing function. (Of course, nesting can be arbitrarily long)

In languages like C++/JAVA/C# it can be easily done by having f1, f2 and f3 throw an exception and enclosing the computation in a try-catch block, while in languages like Haskell we can use monads instead.

Now I am trying to implement it in GO and the only approach I can think of is obvious if-else ladder which is rather verbose. I don't have issue if we can't nest the calls, but in my opinion adding error check after each line in code looks ugly and it breaks the flow. I would like to know if there is any better way of doing it.

Edit: Editing as per the comment by peterSO
Below is the concrete example and straightforward implementation

package main

import "fmt"

func f1(in int) (out int, err error) {
    return in + 1, err
}

func f2(in int) (out int, err error) {
    return in + 2, err
}

func f3(in int) (out int, err error) {
    return in + 3, err
}

func calc(in int) (out int, err error) {
    var temp1, temp2 int
    temp1, err = f1(in)
    if err != nil {
        return temp1, err
    }
    temp2, err = f2(temp1)
    if err != nil {
        return temp2, err
    }
    return f3(temp2)
}

func main() {
     inval := 0
     outval, err := calc3(inval)
     fmt.Println(inval, outval, err)
}

What I am trying to illustrate is, function calc does some computation possibly with the help of library functions that can fail and semantics is if any call fails calc propagates the error to the caller (similar to not handling the exception). In my opinion, code for calc is ugly.

Between for this particular case where all library functions have exactly same signature, we can make the code better (I am using idea from http://golang.org/doc/articles/wiki/#tmp_269)

func saferun(f func (int) (int, error)) func (int, error) (int, error) {
    return func (in int, err error) (int, error) {
        if err != nil {
            return in, err
        }
        return f(in)
    }
} 

Then we can redefine calc as

func calc(in int) (out int, err error) {
    return saferun(f3)(saferun(f2)(f1(in)))
}

or as

func calc(in int) (out int, err error) {
    sf2 := saferun(f2)
    sf3 := saferun(f3)
    return sf3(sf2(f1(in)))
}

But without generics support, I am not sure how I can use this approach for any set of library functions.

like image 908
Suyog Avatar asked Jun 10 '12 08:06

Suyog


People also ask

Can you define a function within a function Golang?

Functions can be nested, instantiated, assigned to variables, and have access to its parent function's variables.

What is function call in Golang?

A function is a group of statements that together perform a task. Every Go program has at least one function, which is main(). You can divide your code into separate functions.

How do you pass a function as argument in Golang?

Golang supports two different ways to pass arguments to the function i.e. Pass by Value or Call by Value and Pass By Reference or Call By Reference. By default, Golang uses the call by value way to pass the arguments to the function.

What is function closure in Golang?

In Golang, a closure is a function that references variables outside of its scope. A closure can outlive the scope in which it was created. Thus it can access variables within that scope, even after the scope is destroyed. Before diving deeper into closures, you need to understand what is an anonymous function.


1 Answers

If you really want to be able to do this you could use a compose function.

func compose(fs ...func(Value) (OutVal, error)) func(Value) (OutVal, error) {
  return func(val Value) OutVal, Error {
    sVal := val
    var err error
    for _, f := range fs {
      sval, err = f(val)
      if err != nil {
        // bail here and return the val
        return nil, err
      }
    }
    return sval, nil
  }
}

outVal, err := compose(f1, f2)(inVal)

Most of the time though you probably want to be more straightforward than this since it may be difficult for others to understand your code when they encounter it.

like image 65
Jeremy Wall Avatar answered Sep 26 '22 02:09

Jeremy Wall