能否根据返回值的赋值来推断类型参数?

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

Is it possible to infer type parameters from what return values are assigned to?

问题

假设我写了两个这样的函数:

func ToInterfaceSlice[T any](s []T) []interface{} {
    res := make([]interface{}, len(s))
    for i, v := range s {
        res[i] = v
    }
    return res
}

func FromInterfaceSlice[T any](s []interface{}) (res []T, err error) {
    res = make([]T, len(s))
    for i, v := range s {
        vt, ok := v.(T)
        if !ok {
            return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
        }
        res[i] = vt
    }
    return
}

当我从输入参数中解析类型时,我可以简单地使用:

var m = []int{1, 2, 3}
fmt.Println(ToInterfaceSlice(m))

编译器知道Tint

然而,当我尝试从返回变量中传递类型时:

var m []int
m, _ = FromInterfaceSlice([]interface{}{1, 2, 3})
fmt.Println(m)

编译器报错:

.\scratch.go:29:27: 无法推断出 T

我必须在函数调用中显式传递类型:

var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
fmt.Println(m)

当接收变量不是接口类型时,是否有什么难以推断类型参数的情况?或者只是没有实现,甚至是故意不实现?

更新 #1(在评论后)

我知道a, b := GenericFunc()不能引用返回值的类型。目前,Go 有一个“取决于”情况,即根据用户输入是否需要显式实例化。

type Set[T comparable] map[T]struct{}

func NewSet[T comparable](eles ...T) Set[T] {
    s := make(Set[T])
    for _, ele := range eles {
        s[ele] = struct{}{}
    }
    return s
}

可以使用t := NewSet(1, 2, 3)t := NewSet[string](),但是现在不能使用var t NewSet[float64] = NewSet(),因为这样会导致问题。

英文:

Suppose I wrote two functions like this:

func ToInterfaceSlice[T any](s []T) []interface{} {
	res := make([]interface{}, len(s))
	for i, v := range s {
		res[i] = v
	}
	return res
}

func FromInterfaceSlice[T any](s []interface{}) (res []T, err error) {
	res = make([]T, len(s))
	for i, v := range s {
		vt, ok := v.(T)
		if !ok {
			return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
		}
		res[i] = vt
	}
	return
}

When I parse type from the input parameters, I can simply use

	var m = []int{1, 2, 3}
	fmt.Println(ToInterfaceSlice(m))

The compiler knows the T is int.

However when I try passing type from the return variables

	var m []int
	m, _ = FromInterfaceSlice([]interface{}{1, 2, 3})
	fmt.Println(m)

The compiler gives error:

> .\scratch.go:29:27: cannot infer T

I must explicitly pass the type in the function call:

	var m []int
	m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
	fmt.Println(m)

Is there anything hard to infer type parameters from return type when the receiver vars are not interface? Or just not implemented, even not to implement on purpose?

Update #1 after the comment

I do know a, b := GenericFunc() cannot refer the type of returned value. Currently Go does have "it depends" case whether requires the explicit instantiation or not from the user input.

type Set[T comparable] map[T]struct{}

func NewSet[T comparable](eles ...T) Set[T] {
	s := make(Set[T])
	for _, ele := range eles {
		s[ele] = struct{}{}
	}
	return s
}

It's okay to use both t := NewSet(1, 2, 3) and t := NewSet[string](), but not var t NewSet[float64] = NewSet() now because of this

答案1

得分: 1

当前的类型推断规则是显式的。不考虑如何使用返回值:

类型推断基于

  • 类型参数列表
  • 一个用已知类型参数初始化的替换映射 M(如果有的话)
  • 一个(可能为空的)普通函数参数列表(仅在函数调用的情况下)

从 Go 1.18 开始,可以简单地重写函数以接受所需类型的参数;这样做的好处还在于不隐藏函数体内的分配:

func FromInterfaceSlice[T any](s []interface{}, dst []T) error {
    if len(s) != len(dst) {
        return errors.New("lengths don't match")
    }
    for i, v := range s {
        vt, ok := v.(T)
        if !ok {
            return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
        }
        dst[i] = vt
    }
    return nil
}

并传入一个具有所需长度的目标切片:

func main() {
    src := []interface{}{1, 2, 3}
    m := make([]int, len(src))
    _ = FromInterfaceSlice(src, m)
    fmt.Println(m)
}

如果无法或不想预先确定切片的长度,则只能使用显式实例化:

var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
//                        ^^^ 显式类型参数

此外,类型参数在使用 := 简短声明时仍然无法推断出来:

// m 是什么类型???
m, err := FromInterfaceSlice([]interface{}{1, 2, 3})
英文:

The current rules for type inference are explicit. How the return values are used is not taken into account:

> Type inference is based on
>
> - a type parameter list
> - a substitution map M initialized with the known type arguments, if any
> - a (possibly empty) list of ordinary function arguments (in case of a function call only)

As of Go 1.18 might simply rewrite your function to accept an argument of the required type; this has also the benefit of not hiding allocations inside the function body:

func FromInterfaceSlice[T any](s []interface{}, dst []T) error {
    if len(s) != len(dst) {
        return errors.New("lengths don't match")
    }
    for i, v := range s {
        vt, ok := v.(T)
        if !ok {
            return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
        }
        dst[i] = vt
    }
    return nil
}

And pass in a destination slice with the required length:

func main() {
	src := []interface{}{1, 2, 3}
	m := make([]int, len(src))
	_ = FromInterfaceSlice(src, m)
	fmt.Println(m)
}

If you can't or don't want to determine the slice's length beforehand, you are left with explicit instantiation:

var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
//                        ^^^ explicit type argument

<hr>

Also the type parameters are still not inferrable with := shorthand declaration:

// what is m???
m, err := FromInterfaceSlice([]interface{}{1, 2, 3})

huangapple
  • 本文由 发表于 2022年5月9日 23:29:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/72174526.html
匿名

发表评论

匿名网友

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

确定