3

I am working on a ETL type application and a large majority of the error handling I do is just retrying API requests until they succeed (they randomly fail on occasion due to connection etc.). Therefore I have noticed a lot of code duplication that looks a lot like

for err != nil {
    a,b,err = myfunc(c, d, e)
}
return a, b

So basically just keep doing the function until the error goes away (I put a sleep and other error checking as necessary as well to avoid rate limiting).

What I would like to do is simplify this to just one function that takes an arbitrary function, finds the error type in its output (if it has one) and runs it recursively until err!=nil. My problem is that although go seems to let you use any (interface{}) as an input it is not variadic on function definitions e.g. (type func(a int) (int, int, error)) as the type func(...any) []any

I am wondering if this is impossible to do in go and if so any suggestions to get around it/get similar functionality more idiomatically.

Trying to test with something like this but the compiler does not like it.

func main() {
    Deal(SometimesFail, 10)
}

func Deal(f func(...any) []any, inputs ...any) []any {
    outputs := f(inputs)
    for _, val := range outputs {
        err, ok := val.(error)
        if ok {
            for err != nil {
                outputs = Deal(f, inputs...)
            }
            return outputs
        }
        continue
    }
    return outputs
} 

func SometimesFail(a int) (int, int, error) {
    random := rand.Intn(a)
    if random%2 == 0 {
        return random, random, nil
    } else {
        return random, random, errors.New("error")
    }
}

I guess what I could do to get around this is create a type for each function out/input scheme and allow the generic function to take any of these. This would keep the code duplication at a minimum while still achieving the goal.

1 Answer 1

1

The following: func(any), func(any, any), func(...any) are all different types and you can't assign one type to another. There is no single function type that would include all of them.

One way to work around this is to decouple function invocation (which must know the exact type of the function) from the retrial logic:

type result struct {
    vals []any
    err  error
}

func main() {
    vals := repeatUntilSuccess(func(inputs ...any) result {
        val, err := failingRandomly(10)
        return result{[]any{val}, err}
    })

    fmt.Println(vals)
}

func repeatUntilSuccess(fn func(...any) result) []any {
    res := fn()
    for res.err != nil {
        res = fn()
    }
    return res.vals
}

func failingRandomly(i int) (int, error) {
    random := rand.Intn(i)
    if random%2 == 0 {
        return random, nil
    } else {
        return random, errors.New("bad luck")
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.