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.
The discussion between Errors vs Exceptions is a long and tedious one. I shall therefore not go into it.
The simplest answer to your question concerns Go’s built-in
defer,panic, andrecoverfunctions as discussed in this blog post. They can offer behaviour similar to exceptions.