Go 泛型 - 联合

Ada*_*dam 14 generics go

我正在通过修改我为处理切片而创建的库来尝试泛型。我有一个Difference函数,它接受切片并返回仅在其中一个切片中找到的唯一元素的列表。

\n

我修改了该函数以使用泛型,并且我正在尝试使用不同类型(例如字符串和整数)编写单元测试,但在联合类型方面遇到了麻烦。就是我现在所拥有的:

\n
type testDifferenceInput[T comparable] [][]T\ntype testDifferenceOutput[T comparable] []T\ntype testDifference[T comparable] struct {\n    input testDifferenceInput[T]\n    output testDifferenceOutput[T]\n}\n\nfunc TestDifference(t *testing.T) {\n        for i, tt := range []testDifference[int] {\n            testDifference[int]{\n                input: testDifferenceInput[int]{\n                    []int{1, 2, 3, 3, 4},\n                    []int{1, 2, 5},\n                    []int{1, 3, 6},\n                },\n                output: []int{4, 5, 6},\n            },\n        } {\n            t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {\n                actual := Difference(tt.input...)\n\n                if !isEqual(actual, tt.output) {\n                    t.Errorf("expected: %v %T, received: %v %T", tt.output, tt.output, actual, actual)\n                }\n        })\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我希望能够在同一个表测试中测试 int 或 string。这是我尝试过的:

\n
type intOrString interface {\n    int | string\n}\ntype testDifferenceInput[T comparable] [][]T\ntype testDifferenceOutput[T comparable] []T\ntype testDifference[T comparable] struct {\n    input testDifferenceInput[T]\n    output testDifferenceOutput[T]\n}\n\nfunc TestDifference(t *testing.T) {\n        for i, tt := range []testDifference[intOrString] {\n            testDifference[int]{\n                input: testDifferenceInput[int]{\n                    []int{1, 2, 3, 3, 4},\n                    []int{1, 2, 5},\n                    []int{1, 3, 6},\n                },\n                output: []int{4, 5, 6},\n            },\n            testDifference[string]{\n                input: testDifferenceInput[string]{\n                    []string{"1", "2", "3", "3", "4"},\n                    []string{"1", "2", "5"},\n                    []string{"1", "3", "6"},\n                },\n                output: []string{"4", "5", "6"},\n            },\n        } {\n            t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {\n                actual := Difference(tt.input...)\n\n                if !isEqual(actual, tt.output) {\n                    t.Errorf("expected: %v %T, received: %v %T", tt.output, tt.output, actual, actual)\n                }\n        })\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但是,运行此程序时,我收到以下错误:

\n
$ go version\ngo version dev.go2go-55626ee50b linux/amd64\n\n$ go tool go2go test\narrayOperations_unit_test.go2:142:6: expected \';\', found \'|\' (and 5 more errors)\n
Run Code Online (Sandbox Code Playgroud)\n

为什么它抱怨我的intOrString界面?

\n

编辑 #1 - 在@Nulo 的帮助下,我可以确认gotip确实有效,而且我现在明白为什么我不能用作intOrString类型 - 它应该是一个约束。

\n

然而,在我的表测试中找到某种方法来混合整数和字符串仍然是件好事......

\n
$ gotip version\ngo version devel go1.18-c812b97 Fri Oct 29 22:29:31 2021 +0000 linux/amd64\n\n$ gotip test\n# github.com/adam-hanna/arrayOperations/go2 [github.com/adam-hanna/arrayOperations/go2.test]\n./arrayOperations_unit_test.go:152:39: interface contains type constraints\n./arrayOperations_unit_test.go:152:39: intOrString does not satisfy intOrString\n./arrayOperations_unit_test.go:155:6: incompatible type: cannot use []int{\xe2\x80\xa6} (value of type []int) as []intOrString value\n./arrayOperations_unit_test.go:156:6: incompatible type: cannot use []int{\xe2\x80\xa6} (value of type []int) as []intOrString value\n./arrayOperations_unit_test.go:157:6: incompatible type: cannot use []int{\xe2\x80\xa6} (value of type []int) as []intOrString value\n./arrayOperations_unit_test.go:159:13: incompatible type: cannot use []int{\xe2\x80\xa6} (value of type []int) as testDifferenceOutput[intOrString] value\n./arrayOperations_unit_test.go:163:6: incompatible type: cannot use []string{\xe2\x80\xa6} (value of type []string) as []intOrString value\n./arrayOperations_unit_test.go:164:6: incompatible type: cannot use []string{\xe2\x80\xa6} (value of type []string) as []intOrString value\n./arrayOperations_unit_test.go:165:6: incompatible type: cannot use []string{\xe2\x80\xa6} (value of type []string) as []intOrString value\n./arrayOperations_unit_test.go:167:13: incompatible type: cannot use []string{\xe2\x80\xa6} (value of type []string) as testDifferenceOutput[intOrString] value\n./arrayOperations_unit_test.go:152:39: too many errors\nFAIL    github.com/adam-hanna/arrayOperations/go2 [build failed]\n
Run Code Online (Sandbox Code Playgroud)\n

bla*_*een 29

如果您因为它的通用标题(不是双关语)而遇到此问答,这里有一个关于工会的快速入门:

\n
    \n
  1. 可用于指定接口约束的类型集。泛型类型参数T将仅限于联合中的类型
  2. \n
  3. 只能在接口约束中使用。如果一个接口包含一个并集(具有一个或多个术语),那么它就是一个接口约束。
  4. \n
  5. 可以包含近似元素~
  6. \n
\n

例如:

\n
type intOrString interface {\n    int | string\n}\n\nfunc Foo[T intOrString](x T) {\n    // x can be int or string\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在讨论OP的问题,并提供更多细节:

\n

您不能将接口约束用作类型

\n

通过包含类型集,intOrString成为接口约束,并且明确不支持将其用作类型。允许约束作为普通接口类型

\n
\n

我们现在不建议使用此功能,但可以在该语言的更高版本中考虑该功能。

\n
\n

因此,要做的第一件事就是用作intOrString实际约束,因此在类型参数列表中使用它。下面我替换comparableintOrString

\n
type testDifferenceInput[T intOrString] [][]T\ntype testDifferenceOutput[T intOrString] []T\ntype testDifference[T intOrString] struct {\n    input testDifferenceInput[T]\n    output testDifferenceOutput[T]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这也意味着您不能使用约束来实例化具体类型作为测试切片:

\n
// bad: using intOrString to instantiate a parametrized type\n[]testDifference[intOrString]\n
Run Code Online (Sandbox Code Playgroud)\n

通用容器无法容纳不同类型的物品

\n

您遇到的第二个问题是测试切片包含两个不相关类型的结构。一是testDifference[int],一是testDifference[string]。尽管类型testDifference本身使用联合约束进行参数化,但其具体实例化并不是同一类型。另请参阅了解更多详细信息。

\n

如果您需要一个包含不同类型的切片,您唯一的选择是[]interface{}(或[]any) ...或者,您只需分隔切片:

\n
ttInts := []testDifference[int]{ testDifference[int]{...}, /* etc. */ }\nttStrs := []testDifference[string]{ testDifference[string]{...}, /* etc. */ }\n
Run Code Online (Sandbox Code Playgroud)\n

联合约束允许的操作

\n

仅类型集中所有类型支持的操作。基于类型集的操作

\n
\n

规则是泛型函数可以以parameter\xe2\x80\x98s 约束的类型集的每个成员允许的任何方式使用类型为类型参数的值。

\n
\n

如果存在类似约束,则orint | string允许执行哪些操作?简而言之:intstring

\n
    \n
  • 变量声明 ( var foo T)
  • \n
  • 转换和断言T(x)x.(T),在适当的情况下
  • \n
  • 比较 (==!=
  • \n
  • 排序 ( <<=>>=)
  • \n
  • +运营商
  • \n
\n

因此,您可以有一个intOrString约束,但使用它的函数(包括您的 func Difference)仅限于这些操作。例如:

\n
type intOrString interface {\n    int | string\n}\n\nfunc beforeIntOrString[T intOrString](a, b T) bool {\n    return a < b\n}\n\nfunc sumIntOrString[T intOrString](a, b T) T {\n    return a + b\n}\n\nfunc main() {\n    fmt.Println(beforeIntOrString("foo", "bar")) // false\n    fmt.Println(beforeIntOrString(4, 5)) // true\n\n    fmt.Println(sumIntOrString("foo", "bar")) // foobar\n    fmt.Println(sumIntOrString(10, 5)) // 15\n}\n
Run Code Online (Sandbox Code Playgroud)\n