Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a generic variadic function as a parameter in Go

Tags:

go

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.

like image 542
Benjamin Memisevic Avatar asked Feb 20 '26 10:02

Benjamin Memisevic


1 Answers

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")
    }
}
like image 130
dev.bmax Avatar answered Feb 23 '26 23:02

dev.bmax



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!