英文:
Do Go generics allow for a LINQ to Objects equivalent?
问题
在Go 1.18中引入了泛型之后,现在是否有可能实现类似于C#的LINQ to Objects?
或者说,与C#泛型相比,Go的泛型在原则上是否存在某些缺失,使得这种实现变得困难或不可能?
例如,原始的101个LINQ示例中的第一个示例("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)
}
英文:
With the addition of generics in Go 1.18, would it now be possible to come up with an equivalent of C#'s LINQ to Objects?
Or are Go's generics lacking something in principle, compared to C# generics, that will make that difficult or impossible?
For example, the first of the original 101 LINQ samples ("LowNumbers") could now be implemented in Go with generics roughly like this:
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)
}
答案1
得分: 5
是和不是。
你几乎可以使用链式API实现这一点。
这对于许多标准的LINQ方法有效,例如Skip
、Take
、Where
、First
、Last
等。
不起作用的是,在流/流中需要切换到另一个泛型类型时。
Go泛型不允许方法具有与其定义的接口/结构不同的其他类型参数。
例如,你不能有一个结构Foo[T any]
,然后有一个方法Bar[O any]
。
这对于像Select
这样的方法是必需的,其中输入类型和输出类型不同。
然而,如果你不使用链式调用,而只使用普通函数,那么在功能上你可以接近。
我在这里做到了:https://github.com/asynkron/gofun
这是一个通过模拟协程来实现的完全惰性的可枚举实现。
这里不起作用的是像Zip
这样需要同时枚举两个可枚举对象的函数。(虽然有一些方法可以绕过这个问题,但不太美观)
英文:
Yes and no.
You can almost get there using a chained APIs.
This works for many of the standard LINQ methods, such as Skip
, Take
, Where
, First
, Last
etc.
What doesn't work, is when you need to switch to another generic type within a flow/stream.
Go Generics does not allow methods to have other type argument than the interface/struct for which they are defined.
e.g. you cannot have a struct Foo[T any]
that then has a method Bar[O any]
This is needed for methods like Select
where you have one type for the input and another type for the output.
However, if you don't use chaining and just go for plain functions. then you can get pretty close functionality-wise.
I've done that here: https://github.com/asynkron/gofun
This is a fully lazy enumerable implementation by simulating co-routines.
What doesn't work here is functions like Zip
which needs to enumerate two enumerables at the same time. (although there are ways to hack that. but nothing pretty)
答案2
得分: 1
Go的参数化多态性与C#或Java中泛型的实现之间一个显著的区别是,Go(至今)没有针对类型参数的协变/逆变的语法。
例如,在C#中,你可以编写实现IComparer<T>
接口的代码,并传递派生的容器类;或者在Java中,在流API中使用典型的Predicate<? super T>
。在Go中,类型必须完全匹配,使用不同类型参数实例化泛型类型会产生不同的命名类型,它们无法相互赋值。参见:https://stackoverflow.com/questions/71399641/why-does-go-not-allow-assigning-one-generic-to-another/
此外,Go不是面向对象的,因此没有继承的概念。你可以有实现接口的类型,甚至是参数化的接口。以下是一个人为的示例:
type Equaler[T any] interface {
Equals(T) bool
}
type Vector []int32
func (v Vector) Equals(other Vector) bool {
// 一些逻辑
}
因此,使用此代码,Vector
实现了Equaler
的特定实例,即Equaler[Vector]
。为了明确起见,以下变量声明是合法的:
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
:
// 实现了Equaler[string]的其他随机类型
type MyString 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}))
// 输出[[1 2] [3 4 5] [8]]
strs := []MyString{"foo-bar", "hello-world", "bar-baz"}
fmt.Println(Remove(strs, "hello"))
// 输出[foo-bar bar-baz]
}
唯一的问题是只有定义的类型才能有方法,因此这种方法已经排除了所有复合非命名类型。
然而,Go具有高阶函数,因此使用它和非命名类型编写类似流的API并不是不可能的,例如:
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
filtered := Where(vecs, func(v Vector) bool { return v[0] == 3})
fmt.Printf("%T %v", filtered, filtered)
// 输出[]main.Vector [[3 4 5]]
}
特别是在这里,你使用了一个命名的类型参数C ~[]T
,而不仅仅是定义collection []T
,以便你可以将其与命名和非命名类型一起使用。
代码在playground中可用:https://gotipplay.golang.org/p/mCM2TJ9qb3F
(选择参数化接口还是高阶函数可能取决于许多因素,其中之一是是否想要链式方法,但在Go中,方法链并不常见。)
结论:是否足以模拟类似LINQ或Stream的API,并/或启用大型泛型库,只有实践才能告诉我们。现有的功能非常强大,并且在Go 1.19中,在语言设计师获得有关泛型的实际使用经验后,这些功能可能变得更加强大。
英文:
(Disclaimer: I'm not a C# expert)
A conspicuous difference between Go's parametric polymorphism and the implementation of generics in C# or Java is that Go (still) has no syntax for co-/contra-variance over type parameters.
For example in C# you can have code that implements IComparer<T>
and pass derived container classes; or in Java the typical Predicate<? super T>
in the stream API. In Go, types must match exactly, and instantiating a generic type with different type parameters yields different named types that just can't be assigned to each other. See also: https://stackoverflow.com/questions/71399641/why-does-go-not-allow-assigning-one-generic-to-another/
Also Go is not OO, so there's no concept of inheritance. You may have types that implement interfaces, and even parametrized interfaces. A contrived example:
type Equaler[T any] interface {
Equals(T) bool
}
type Vector []int32
func (v Vector) Equals(other Vector) bool {
// some logic
}
So with this code, Vector
implements a specific instance of Equaler
which is Equaler[Vector]
. To be clear, the following var declaration compiles:
var _ Equaler[Vector] = Vector{}
So with this, you can write functions that are generic in T
and use T
to instantiate Equaler
, and you will be able to pass anything that does implement that specific instance of 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
}
And you can call this function with any T
, and therefore with any T
that has an Equals(T)
method:
// 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]
}
The only problem is that only defined types can have methods, so this approach already excludes all composite non-named types.
However, to a partial rescue, Go has higher-order functions, so writing a stream-like API with that and non-named types is not impossible, e.g.:
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]]
}
In particular here you use a named type parameter C ~[]T
instead of just defining collection []T
so that you can use it with both named and non-named types.
Code available in the playground: https://gotipplay.golang.org/p/mCM2TJ9qb3F
(Choosing the parametrized interfaces vs. higher-order functions probably depends on, among others, if you want to chain methods, but method chaining in Go isn't very common to begin with.)
Conclusion: whether that is enough to mimic LINQ- or Stream-like APIs, and/or enable large generic libraries, only practice will tell. The existing facilities are pretty powerful, and could become even more so in Go 1.19 after the language designers gain additional experience with real-world usage of generics.
答案3
得分: 0
Go泛型目前不支持泛型方法。但是你可以编写泛型函数。
// Filter函数选择f()返回true的项
func Filter[Src any](l []Src, f func(Src) bool) (result []Src) {
result = make([]Src, 0, len(l)/2)
for _, x := range l {
if f(x) {
result = append(result, x)
}
}
return
}
func ExampleFilter() {
lst := []int{1, 2, 3, 4, 5, 6}
isEven := func(n int) bool { return n%2 == 0 } // 期望过滤后的偶数数组
evens := Filter(lst, isEven)
fmt.Println(evens)
// 输出: [2 4 6]
}
你可以在我的代码库中获取Filter()和许多其他有用的工具函数:https://github.com/szmcdull/glinq
英文:
Go generics does not support generic method at the time. But you can write generic functions.
// Filter selects items that f() returns true
func Filter[Src any](l []Src, f func(Src) bool) (result []Src) {
result = make([]Src, 0, len(l)/2)
for _, x := range l {
if f(x) {
result = append(result, x)
}
}
return
}
func ExampleFilter() {
lst := []int{1, 2, 3, 4, 5, 6}
isEven := func(n int) bool { return n%2 == 0 } // expected filtered array of even numbers
evens := Filter(lst, isEven)
fmt.Println(evens)
// Output: [2 4 6]
}
You can get Filter() and many other useful tool functions at my repository: https://github.com/szmcdull/glinq
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论