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.
Functions can be nested, instantiated, assigned to variables, and have access to its parent function's variables.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With