为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
哔哔one 2022-11-23 13:57:36

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

}


查看完整描述

2 回答

?
米脂

TA贡献1560条经验 获得超3个赞

是和不是。

几乎可以使用链式 API 到达那里。这适用于许多标准 LINQ 方法,例如SkipTakeWhereFirstLast

不起作用的是,当您需要切换到流/流中的另一种通用类型时。

Go 泛型不允许方法具有除定义它们的接口/结构之外的其他类型参数。例如,你不能有一个结构Foo[T any],然后有一个方法Bar[O any] 这是方法所必需的,比如Select你有一种类型的输入和另一种类型的输出。

但是,如果您不使用链接而只是使用普通函数。那么您可以获得非常接近的功能。

我已经在这里完成了:https ://github.com/asynkron/gofun

这是一个通过模拟协程实现的完全懒惰的可枚举实现。

在这里不起作用的是Zip需要同时枚举两个可枚举的函数。(虽然有办法破解它。但没什么好看的)


查看完整回答
反对 回复 2022-11-23
?
忽然笑

TA贡献1528条经验 获得超5个赞

Go 的参数多态性与 C# 或 Java 中的泛型实现之间的一个显着区别是 Go(仍然)没有针对类型参数的协方差/反方差的语法。

例如,在 C# 中,您可以拥有实现IComparer<T>和传递派生容器类的代码;或者在 Java 中典型Predicate<? super T>的流 API。在 Go 中,类型必须完全匹配,并且使用不同的类型参数实例化泛型类型会产生不同的命名类型,这些类型不能相互分配。另请参阅:为什么 Go 不允许将一个泛型分配给另一个泛型?

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

}

您可以使用 any 调用此函数T,因此使用T具有Equals(T)方法的 any :


// 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,这样你就可以将它用于命名和非命名类型。


操场上可用的代码:https ://gotipplay.golang.org/p/mCM2TJ9qb3F


(选择参数化接口还是高阶函数可能取决于,除其他外,如果你想链接方法,但 Go 中的方法链接一开始并不是很常见。)


结论:这是否足以模仿 LINQ 或类似 Stream 的 API,和/或启用大型通用库,只有实践才能证明。现有设施非常强大,在语言设计者获得泛型实际使用的额外经验后,Go 1.19 中的功能可能会变得更加强大。


查看完整回答
反对 回复 2022-11-23

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信