Go泛型是否允许LINQ to Objects等效?

chhqkbe1  于 2023-05-20  发布在  Go
关注(0)|答案(3)|浏览(462)

随着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)
}
cld4siwp

cld4siwp1#

是也不是
你几乎可以使用一个链接的API到达那里。这适用于许多标准LINQ方法,如SkipTakeWhereFirstLast等。
不起作用的是,当你需要切换到流/流中的另一个泛型类型时。
Go Generics不允许 * 方法 * 拥有除了接口/结构之外的其他类型参数。例如,你不能有一个结构Foo[T any],然后有一个方法Bar[O any]这是需要的方法,如Select,你有一个类型的输入和另一个类型的输出。
但是,如果你不使用链接,只使用普通函数。那么你就可以得到非常接近的功能。
我在这里做过:https://github.com/asynkron/gofun
这是一个通过模拟协同例程实现的完全惰性可枚举实现。
这里不起作用的是像Zip这样的函数,它需要同时枚举两个枚举。(虽然有办法破解它。(Nothing Pretty)

rqdpfwrv

rqdpfwrv2#

(免责声明:我不是C#Maven)
Go语言的参数多态性与C#或Java中泛型的实现之间的一个显著区别是Go语言(仍然)没有类型参数的协/反方差语法。
例如,在C#中,您可以使用实现IComparer<T>并传递派生容器类的代码;或者Java中典型的Predicate<? super T>流API。在Go语言中,类型必须完全匹配,并且用不同的类型参数示例化泛型类型会产生不同的命名类型,而这些类型只是不能相互赋值。标签:Why does Go not allow assigning one generic to another?
Go不是OO,所以没有继承的概念。您可能有实现接口的类型,甚至参数化接口。一个人为的例子:

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]
}

唯一的问题是只有定义的类型才能有方法,所以这种方法已经排除了所有复合的非命名类型。
然而,为了部分补救,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 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,这样您就可以同时使用命名类型和非命名类型。
Playground可用代码:https://gotipplay.golang.org/p/mCM2TJ9qb3F
(选择参数化接口与高阶函数可能取决于,如果你想链接方法,但在Go语言中方法链接并不常见。

结论:这是否足以模仿LINQ或Stream-like API,和/或启用大型通用库,只有实践才能说明问题。现有的功能非常强大,在语言设计者获得了泛型的真实使用经验之后,Go 1.19中可能会变得更加强大。

wbgh16ku

wbgh16ku3#

Go泛型目前不支持泛型方法。但是你可以编写泛型函数。

// 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]
}

你可以在我的存储库中获得Filter()和许多其他有用的工具函数:https://github.com/szmcdull/glinq

相关问题