Go 泛型是否允许 LINQ to Objects 等效?
Do Go generics allow for a LINQ to Objects equivalent?
与addition of generics in Go 1.18, would it now be possible to come up with an equivalent of C#'s LINQ to Objects?
或者与 C# 泛型相比,Go 的泛型在原则上是否缺少某些东西,这会使它变得困难或不可能?
例如,最初的 101 LINQ samples(“LowNumbers”)中的第一个现在可以使用泛型在 Go 中实现,大致如下:
package main
import (
"fmt"
)
type collection[T comparable] []T
func (input collection[T]) where(pred func(T) bool) collection[T] {
result := collection[T]{}
for _, j := range input {
if pred(j) {
result = append(result, j)
}
}
return result
}
func main() {
numbers := collection[int]{5, 4, 1, 3, 9, 8, 6, 7, 2, 0}
lowNums := numbers.where(func(i int) bool { return i < 5 })
fmt.Println("Numbers < 5:")
fmt.Println(lowNums)
}
(免责声明:我不是 C# 专家)
Go 的参数多态性与 C# 或 Java 中的泛型实现之间的一个显着区别是 Go(仍然)没有 co-/contra-variance 类型参数的语法。
例如,在 C# 中,您可以拥有实现 IComparer<T>
并传递派生容器 类 的代码;或者在 Java 流 API 中典型的 Predicate<? super T>
。在 Go 中,类型必须完全匹配,并且使用不同的类型参数实例化泛型类型会产生不同的命名类型,这些类型不能相互分配。另见:
而且Go不是面向对象的,所以没有继承的概念。您可能有实现接口的类型,甚至是参数化接口。一个人为的例子:
type Equaler[T any] interface {
Equals(T) bool
}
type Vector []int32
func (v Vector) Equals(other Vector) bool {
// some logic
}
因此,使用此代码,Vector
实现 Equaler
的特定实例 ,即 Equaler[Vector]
。明确地说,以下 var 声明编译:
var _ Equaler[Vector] = Vector{}
因此,您可以编写 T
中的通用函数并使用 T
实例化 Equaler
,您将能够传递任何实现该特定功能的函数Equaler
的实例:
func Remove[E Equaler[T], T any](es []E, v T) []E {
for i, e := range es {
if e.Equals(v) {
return append(es[:i], es[i+1:]...)
}
}
return es
}
并且您可以使用任何 T
调用此函数,因此可以使用具有 Equals(T)
方法的任何 T
:
// some other random type that implements Equaler[T]
type MyString string
// implements Equaler[string]
func (s MyString) Equals(other string) bool {
return strings.Split(string(s), "-")[0] == other
}
func main() {
vecs := []Vector{{1, 2}, {3, 4, 5}, {6, 7}, {8}}
fmt.Println(Remove(vecs, Vector{6, 7}))
// prints [[1 2] [3 4 5] [8]]
strs := []MyString{"foo-bar", "hello-world", "bar-baz"}
fmt.Println(Remove(strs, "hello"))
// prints [foo-bar bar-baz]
}
唯一的问题是只有定义的类型才能有方法,所以这种方法已经排除了所有复合 non-named 类型。
然而,为了部分挽救,Go 有 higher-order 函数,所以写 stream-like API 和 non-named 类型并非不可能,例如:
func Where[C ~[]T, T any](collection C, predicate func(T) bool) (out C) {
for _, v := range collection {
if predicate(v) {
out = append(out, v)
}
}
return
}
func main() {
// vecs declared earlier
filtered := Where(vecs, func(v Vector) bool { return v[0] == 3})
fmt.Printf("%T %v", filtered, filtered)
// prints []main.Vector [[3 4 5]]
}
特别是在这里你使用命名类型参数 C ~[]T
而不是仅仅定义 collection []T
这样你就可以将它与 both named 和 non-named 类型。
操场上可用的代码:https://gotipplay.golang.org/p/mCM2TJ9qb3F
(选择参数化接口与 higher-order 函数可能取决于,除其他外,如果你想链接方法,但 Go 中的方法链接并不是很常见。)
结论:是否足以模仿LINQ-或Stream-likeAPIs,and/or启用大型泛型库,仅实践将会说。现有设施非常强大,在语言设计者获得更多 real-world 使用泛型的经验后,在 Go 1.19 中可能会变得更加强大。
是也不是。
您可以几乎使用链式 API 到达那里。
这适用于许多标准 LINQ 方法,例如 Skip
、Take
、Where
、First
、Last
等
当您需要在 flow/stream.
中切换到另一种泛型时,这是行不通的
Go Generics 不允许 methods 具有除它们定义的 interface/struct 之外的其他类型参数。
例如你不能有一个结构 Foo[T any]
然后有一个方法 Bar[O any]
这对于像 Select
这样的方法是必需的,其中您有一种输入类型和另一种输出类型。
但是,如果您不使用链接而只使用普通函数。那么你可以很接近 functionality-wise.
我已经做到了:https://github.com/asynkron/gofun
这是一个通过模拟 co-routines.
实现的完全懒惰的可枚举实现
这里不起作用的是 Zip
之类的函数,它需要同时枚举两个可枚举项。 (虽然有办法破解它。但没什么好看的)
与addition of generics in Go 1.18, would it now be possible to come up with an equivalent of C#'s LINQ to Objects?
或者与 C# 泛型相比,Go 的泛型在原则上是否缺少某些东西,这会使它变得困难或不可能?
例如,最初的 101 LINQ samples(“LowNumbers”)中的第一个现在可以使用泛型在 Go 中实现,大致如下:
package main
import (
"fmt"
)
type collection[T comparable] []T
func (input collection[T]) where(pred func(T) bool) collection[T] {
result := collection[T]{}
for _, j := range input {
if pred(j) {
result = append(result, j)
}
}
return result
}
func main() {
numbers := collection[int]{5, 4, 1, 3, 9, 8, 6, 7, 2, 0}
lowNums := numbers.where(func(i int) bool { return i < 5 })
fmt.Println("Numbers < 5:")
fmt.Println(lowNums)
}
(免责声明:我不是 C# 专家)
Go 的参数多态性与 C# 或 Java 中的泛型实现之间的一个显着区别是 Go(仍然)没有 co-/contra-variance 类型参数的语法。
例如,在 C# 中,您可以拥有实现 IComparer<T>
并传递派生容器 类 的代码;或者在 Java 流 API 中典型的 Predicate<? super T>
。在 Go 中,类型必须完全匹配,并且使用不同的类型参数实例化泛型类型会产生不同的命名类型,这些类型不能相互分配。另见:
而且Go不是面向对象的,所以没有继承的概念。您可能有实现接口的类型,甚至是参数化接口。一个人为的例子:
type Equaler[T any] interface {
Equals(T) bool
}
type Vector []int32
func (v Vector) Equals(other Vector) bool {
// some logic
}
因此,使用此代码,Vector
实现 Equaler
的特定实例 ,即 Equaler[Vector]
。明确地说,以下 var 声明编译:
var _ Equaler[Vector] = Vector{}
因此,您可以编写 T
中的通用函数并使用 T
实例化 Equaler
,您将能够传递任何实现该特定功能的函数Equaler
的实例:
func Remove[E Equaler[T], T any](es []E, v T) []E {
for i, e := range es {
if e.Equals(v) {
return append(es[:i], es[i+1:]...)
}
}
return es
}
并且您可以使用任何 T
调用此函数,因此可以使用具有 Equals(T)
方法的任何 T
:
// some other random type that implements Equaler[T]
type MyString string
// implements Equaler[string]
func (s MyString) Equals(other string) bool {
return strings.Split(string(s), "-")[0] == other
}
func main() {
vecs := []Vector{{1, 2}, {3, 4, 5}, {6, 7}, {8}}
fmt.Println(Remove(vecs, Vector{6, 7}))
// prints [[1 2] [3 4 5] [8]]
strs := []MyString{"foo-bar", "hello-world", "bar-baz"}
fmt.Println(Remove(strs, "hello"))
// prints [foo-bar bar-baz]
}
唯一的问题是只有定义的类型才能有方法,所以这种方法已经排除了所有复合 non-named 类型。
然而,为了部分挽救,Go 有 higher-order 函数,所以写 stream-like API 和 non-named 类型并非不可能,例如:
func Where[C ~[]T, T any](collection C, predicate func(T) bool) (out C) {
for _, v := range collection {
if predicate(v) {
out = append(out, v)
}
}
return
}
func main() {
// vecs declared earlier
filtered := Where(vecs, func(v Vector) bool { return v[0] == 3})
fmt.Printf("%T %v", filtered, filtered)
// prints []main.Vector [[3 4 5]]
}
特别是在这里你使用命名类型参数 C ~[]T
而不是仅仅定义 collection []T
这样你就可以将它与 both named 和 non-named 类型。
操场上可用的代码:https://gotipplay.golang.org/p/mCM2TJ9qb3F
(选择参数化接口与 higher-order 函数可能取决于,除其他外,如果你想链接方法,但 Go 中的方法链接并不是很常见。)
结论:是否足以模仿LINQ-或Stream-likeAPIs,and/or启用大型泛型库,仅实践将会说。现有设施非常强大,在语言设计者获得更多 real-world 使用泛型的经验后,在 Go 1.19 中可能会变得更加强大。
是也不是。
您可以几乎使用链式 API 到达那里。
这适用于许多标准 LINQ 方法,例如 Skip
、Take
、Where
、First
、Last
等
当您需要在 flow/stream.
中切换到另一种泛型时,这是行不通的Go Generics 不允许 methods 具有除它们定义的 interface/struct 之外的其他类型参数。
例如你不能有一个结构 Foo[T any]
然后有一个方法 Bar[O any]
这对于像 Select
这样的方法是必需的,其中您有一种输入类型和另一种输出类型。
但是,如果您不使用链接而只使用普通函数。那么你可以很接近 functionality-wise.
我已经做到了:https://github.com/asynkron/gofun
这是一个通过模拟 co-routines.
实现的完全懒惰的可枚举实现这里不起作用的是 Zip
之类的函数,它需要同时枚举两个可枚举项。 (虽然有办法破解它。但没什么好看的)