有了addition of generics in Go 1.18,现在是否有可能得出C#的LINQ to Objects

或者说,与C#泛型相比,Go的泛型在原则上缺乏一些东西,这会让它变得困难或不可能?

例如,最初的101 LINQ samples个("LowNumber")中的第一个现在可以与泛型一起实现,大致如下所示:

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(仍然)没有针对类型参数的协变/反变的语法.

例如,在C#中,可以有实现IComparer<T>和传递派生容器类的代码;或者在Java中,流API中典型的Predicate<? super T>.在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中的a specific instance,也就是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调用这个函数,因此用任何T调用Equals(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,以便可以将其用于both个命名和非命名类型.

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

( Select 参数化接口与高阶函数可能取决于是否要链接方法,但Go中的方法链接一开始并不常见.)

Conclusion:这是否足以模仿LINQ或流式API,和/或启用大型通用库,只有实践才能说明.现有的功能非常强大,在Go 1.19中,在语言设计师获得了泛型的实际使用经验之后,可能会变得更加强大.

Go相关问答推荐

在保留额外参数的同时解封YAML

验证访问令牌(密钥罩)

使用一元或服务器流将切片从GRPC服务器返回到客户端

如何使用GO GIN从Auth0 JWT内标识检索权限

Golang中的泛型 struct /接口列表

Prometheus 摘要分位数错误

如何测试 Zerolog 记录器引发类型错误的日志(log)事件?

Golang chromedp Dockerfile

如何将任何类型的数据值传递到 Golang 中的 GRPC Protobuf struct ?

Exchange Web 服务 - 使用 soap xml 请求查找所有未读邮件

如何使用 go-git 将特定分支推送到远程

将 big.Int 转换为 [2]int64,反之亦然和二进制补码

如何编写一个以字符串或错误为参数的通用函数?

Golang grpc go.mod 问题

没有堆栈跟踪的 go 程序崩溃是什么意思?

Golang 数据库/sql 与 SetMaxOpenConns 挂起

具有近似约束的函数值导致的实例化失败

为什么我不能使用来自 gocloak 中 Login() 的访问令牌在 KeyCloak 中创建新客户端?

(如何)我可以基于接口抽象地实现Stringer吗?

我应该明确地创建一个与Belongs To或Has Many对称的关系吗?