如何执行一个类型为多个返回类型约束的泛型类型的回调函数?

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

How to execute a callback who's type is a generic type constrained with multiple return types?

问题

我正在尝试在我的函数中允许一个回调参数,该参数允许多个返回元组。为了实现这一点,我使用泛型来定义回调的参数类型。

func Get[
    In any,
    Out any,
    TranslateFn func(In) Out | func(In) (Out, error),
](
    input In,
    translate TranslateFn,
) (*Out, error) {
    // 调用 translate 将输入转换为输出。
    // 如果 translate 是一个出错的函数,确保错误被转发到该函数的响应中。
}

由于 TranslateFn 受限于这两种返回类型之一(要么是 Out,要么是 (Out, error)),我假设我可以调用它。

我想做的是像下面这样,但是我不能这样做,因为我无法对 translate 参数使用类型断言。

invalid operation: cannot use type assertion on type parameter value translate (variable of type TranslateFn constrained by func(In) Out|func(In) (Out, error))

func Get[
    In any,
    Out any,
    TranslateFn func(In) Out | func(In) (Out, error),
](
    input In,
    translate TranslateFn,
) (*Out, error) {
    if erroringFn, isErroringTranslateFn := translate.(func(In) (Out, error)); isErroringTranslateFn {
        out, err := erroringFn(input)
        if err != nil {
            return nil, err
        }
        return &out, nil
    }

    if nonErroringFn, isNonErroringTranslateFn := translate.(func(In) Out); isNonErroringTranslateFn {
        out, err := nonErroringFn(input)
        if err != nil {
            return nil, err
        }
        return &out, nil
    }

    panic("translate function must be either func(In) (Out, error) or func(In) Out")
}

在没有类型断言的情况下,我该如何调用这个回调函数,或者确定提供了哪个泛型变体呢?

英文:

I'm trying to allow a callback parameter in my function that permits multiple return tuples. To achieve this, I'm using generics to define the parameter type for the callback.

func Get[
	In any,
	Out any,
	TranslateFn func(In) Out | func(In) (Out, error),
](
	input In,
	translate TranslateFn,
) (*Out, error) {
	// Call translate to convert the input to the output.
    // If translate is an erroring function, make sure the error is
    // forwarded to the response of this function.
}

Since TranslateFn is constrained to either of those two return types (either Out or (Out, error)), I assumed I would be able to call it.

What I'd like to do is something like the following, however I cannot because I'm unable to use type assertion on the translate argument.

> invalid operation: cannot use type assertion on type parameter value translate (variable of type TranslateFn constrained by func(In) Out|func(In) (Out, error))

func Get[
	In any,
	Out any,
	TranslateFn func(In) Out | func(In) (Out, error),
](
	input In,
	translate TranslateFn,
) (*Out, error) {
	if erroringFn, isErroringTranslateFn := translate.(func(In) (Out, error)); isErroringTranslateFn {
		out, err := erroringFn(input)
		if err != nil {
			return nil, err
		}
		return &out, nil
	}

	if nonErroringFn, isNonErroringTranslateFn := translate.(func(In) Out); isNonErroringTranslateFn {
		out, err := nonErroringFn(input)
		if err != nil {
			return nil, err
		}
		return &out, nil
	}

	panic("translate function must be either func(In) (Out, error) or func(In) Out")
}

Without type assertion, how would I go about invoking this callback, or determining which of the generic variations was provided?

答案1

得分: 3

首先将其封装为一个接口,然后进行类型断言(或类型切换)。例如:any(v).(T)

func Get[
    In any,
    Out any,
    TranslateFn func(In) Out | func(In) (Out, error),
](
    input In,
    translate TranslateFn,
) (*Out, error) {
    switch f := any(translate).(type) {
    case func(In) (Out, error):
        out, err := f(input)
        if err != nil {
            return nil, err
        }
        return &out, nil
    case func(In) Out:
        out := f(input)
        return &out, nil
    }

    panic("shouldn't happen")
}
英文:

Wrap it into an interface first and then do type assertion (or type switch). E.g. any(v).(T)

func Get[
	In any,
	Out any,
	TranslateFn func(In) Out | func(In) (Out, error),
](
	input In,
	translate TranslateFn,
) (*Out, error) {
	switch f := any(translate).(type) {
	case func(In) (Out, error):
		out, err := f(input)
		if err != nil {
			return nil, err
		}
		return &out, nil
	case func(In) Out:
		out := f(input)
		return &out, nil
	}

	panic("shouldn't happen")
}

答案2

得分: 3

如mkopriva的答案所建议的,类型切换是一种选择,但一般来说,我建议不要同时允许两种类型的回调函数。相反,我会这样做:

func Get[
    In any,
    Out any,
    TranslateFn func(In) (Out, error),
](
    input In,
    translate TranslateFn,
) (*Out, error) {
    o, err := translate(in)
    if err != nil {
         return nil, err
    }
    return &o, nil
}

现在,如果你有一堆函数想要使用,但其中相当一部分不返回错误,你可以提供一个方便的包装函数:

func ErrWrap[In any, Out any] (cb func(In) Out) func (In) (Out, error) {
    return func (i In) (Out, error) {
        o := cb(i)
        return o, nil
    }
}

通过这种方式,所有不返回错误的函数都可以被整洁地包装成返回错误值的函数(该值将始终为nil)。

好处是它简化了你当前的Get函数,使其更加清晰、易读和易于维护(对他人和你自己而言)。从现在开始,它清楚地传达了Get函数期望一个返回类型为Out和错误的回调函数,但现有函数(及其调用位置)不需要立即重构以适应第二个返回值,尽管你应该知道,对ErrWrap的所有调用都是技术债的指标。

英文:

As mkopriva's answer suggests, a type switch is an option, but generally speaking, I would advise against allowing both types of, what is essentially, callbacks. Instead I'd just do something like this:

func Get[
    In any,
    Out any,
    TranslateFn func(In) (Out, error),
](
    input In,
    translate TranslateFn,
) (*Out, error) {
    o, err := translate(in)
    if err != nil {
         return nil, err
    }
    return &o, nil
}

Now, if you have a bunch of functions you want to use, but a significant proportion of them don't return an error, you can provide a convenience wrapper function:

func ErrWrap[In any, Out any] (cb func(In) Out) func (In) (Out, error) {
    return func (i In) (Out, error) {
        o := cb(i)
        return o, nil
    }
}

With this, all functions that don't return an error can be neatly wrapped to return an error value (which will simply always be nil).

The benefit is that it simplifies your current Get function, so it's just a lot cleaner, and easier to read/maintain for others (and yourself). Going forwards, it communicates quite clearly that Get expects a callback that returns a value of type Out, and an error, but existing functions (and places they are called) needn't be immediately refactored to account for a second return value, although you should know that all calls to ErrWrap are indicators of technical debt.

huangapple
  • 本文由 发表于 2023年4月4日 23:39:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75931290.html
匿名

发表评论

匿名网友

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

确定