泛型类型推断在级联调用泛型函数时的应用

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

Generics type inference when cascaded calls of generic functions

问题

在构建一个(我第一个)泛型重型库时,我遇到了一些泛型类型检查实现的明显限制——更可能是我知识的不足。

有没有办法让下面这样的代码工作?

package main

import (
	"fmt"
	"reflect"
)

type Number interface {
	int | float32
}

type MultiDimensionSlice interface {
	int | float32 | []int | []float32 | [][]int | [][]float32
}

func dimensions[S MultiDimensionSlice](s S) int {
	dims := 0
	t := reflect.TypeOf(s)
	for t.Kind() == reflect.Slice {
		dims += 1
		t = t.Elem()
	}
	return dims
}

func indirection[T Number](v T) int {
	var slice2D [][]T
	return dimensions(slice2D)
}

func main() {
	x := [][]float32{{1}, {2}, {3}}
	fmt.Printf("x=%v, dims=%d\n", x, dimensions(x))
	fmt.Printf("indirection should return 2, got %d\n", indirection(0))
}

这段代码无法编译通过,报错信息为 [][]T does not implement MultiDimensionSlice ([][]T missing in int | float32 | []int | []float32 | [][]int | [][]float32)

但是在函数 indirection() 中,所有允许的 T 值都将在 dimensions() 中有一个实现。

非常感谢任何帮助或指导!

Playground 链接

附注:我的问题比这个复杂一些,但问题是一个泛型函数(在这个例子中是 indirection())无法调用另一个泛型函数(在这里是 dimensions()),因为(显然)Go编译器无法解析类型参数的约束(这些信息在编译时是可用的...)。

英文:

While building a (my first) generics heavy library, and I'm bumping on some apparent limitations on the generics type checking implementation -- more likely my lack of knowledge.

Any ideas how to get something like below to work ?

package main

import (
	"fmt"
	"reflect"
)

type Number interface {
	int | float32
}

type MultiDimensionSlice interface {
	int | float32 | []int | []float32 | [][]int | [][]float32
}

func dimensions[S MultiDimensionSlice](s S) int {
	dims := 0
	t := reflect.TypeOf(s)
	for t.Kind() == reflect.Slice {
		dims += 1
		t = t.Elem()
	}
	return dims
}

func indirection[T Number](v T) int {
	var slice2D [][]T
	return dimensions(slice2D)
}

func main() {
	x := [][]float32{{1}, {2}, {3}}
	fmt.Printf("x=%v, dims=%d\n", x, dimensions(x))
	fmt.Printf("indirection should return 2, got %d\n", indirection(0))
}

This fails to compile with the message [][]T does not implement MultiDimensionSlice ([][]T missing in int | float32 | []int | []float32 | [][]int | [][]float32)

But within the function indirection() all the allowed values of T will have an implementation in dimensions().

Any help or pointers would be mostly appreciated!

(Playground link)

ps.: My problem is a bit more complex than that, but the issue is that one generic function (indirection() in this example) is not able to invoke the other (dimensions() here) because (apparently) Go compiler is not able to resolve the type parameter constraints (the information is there in compile time...).

答案1

得分: 2

如评论中所提到的,Go语言在泛型方面有一些限制。您可以通过一种变通方法实现您所需的功能。

首先,您需要更改您定义的接口(也使其成为泛型):

type Number interface {
	int | float32
}

type MultiDimensionSlice[T Number] interface {
	Number | []T | [][]T
}

然后,我们需要更改dimension方法的类型参数。如果Go语言允许我们像这样定义dimensions方法,代码会更加清晰:

func dimensions[S Number](s MultiDimensionSlice[S]) int {

但我们能做的只有:

func dimensions[S Number, K MultiDimensionSlice[S]](s K) int {
	dims := 0
	t := reflect.TypeOf(s)
	for t.Kind() == reflect.Slice {
		dims += 1
		t = t.Elem()
	}
	return dims
}

然后,我们需要更改如何调用dimensions方法。我们需要提供额外的类型参数,以便Go可以推断出类型参数S

func indirection[T Number](v T) int {
	var slice2D [][]T
	return dimensions[T](slice2D)
}

func main() {
	x := [][]float32{{1}, {2}, {3}}
	fmt.Printf("x=%v, dims=%d\n", x, dimensions[float32](x))
	fmt.Printf("indirection should return 2, got %d\n", indirection(0))
}

Playground

英文:

As mentioned in the comments go has some restrictions in its generics perspective. You can achieve what you require with a workaround.

First, you need to change the interface you define. (Make it generic too)

type Number interface {
	int | float32
}

type MultiDimensionSlice[T Number] interface {
	Number | []T | [][]T
}

Then we need to change dimension methods type arguments. It would be much cleaner if go would let us define dimensions method like this

func  dimensions[S Number](s MultiDimensionSlice[S]) int {

But all we can do is :

func dimensions[S Number, K MultiDimensionSlice[S]](s K) int {
	dims := 0
	t := reflect.TypeOf(s)
	for t.Kind() == reflect.Slice {
		dims += 1
		t = t.Elem()
	}
	return dims
}

And then we need to change how we call dimensions method. We need to provide an extra type argument in order to go can infer type argument S

func indirection[T Number](v T) int {
	var slice2D [][]T
	return dimensions[T](slice2D)
}

func main() {
	x := [][]float32{{1}, {2}, {3}}
	fmt.Printf("x=%v, dims=%d\n", x, dimensions[float32](x))
	fmt.Printf("indirection should return 2, got %d\n", indirection(0))
}

Playground

huangapple
  • 本文由 发表于 2022年9月3日 17:35:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/73591149.html
匿名

发表评论

匿名网友

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

确定