在Go语言中将一个通用的可变参数函数作为参数传递

huangapple go评论82阅读模式
英文:

Pass a generic variadic function as a parameter in Go

问题

我正在开发一个ETL类型的应用程序,其中大部分的错误处理都是通过重试API请求直到成功(由于连接等原因,它们偶尔会随机失败)来完成的。
因此,我注意到了很多重复的代码,看起来很像这样:

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

所以基本上就是一直执行函数,直到错误消失(我还添加了必要的睡眠和其他错误检查,以避免速率限制)。

我想做的是将其简化为只有一个函数,该函数接受任意函数作为参数,查找其输出中的错误类型(如果有的话),并递归运行直到err!=nil。我的问题是,虽然Go似乎允许您将任何(interface{})作为输入,但在函数定义上它不是可变参数的,例如
(type func(a int) (int, int, error)) 作为类型 func(...any) []any

我想知道在Go中是否不可能做到这一点,如果不可能的话,是否有任何建议可以绕过它/以更符合惯用法的方式实现类似的功能。

尝试使用类似以下的代码进行测试,但编译器不喜欢它。

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 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

得分: 1

以下是翻译好的内容:

以下的 func(any)func(any, any)func(...any) 都是不同的类型,你不能将一个类型赋值给另一个类型。没有一个单一的函数类型可以包含它们所有。

解决这个问题的一种方法是将函数调用(必须知道函数的确切类型)与重试逻辑分离开来:

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")
    }
}
英文:

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")
	}
}

huangapple
  • 本文由 发表于 2023年1月17日 08:28:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/75140971.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定