Go1.18 泛型的好、坏亦或丑?

Go 泛型定了,有哪些好的使用场景,哪些不好的应用场景,亦或哪些使用看起来丑?本文聊聊这个问题 。
01 简介泛型很棒,而且 Go 变得比以前更方便了 。但是与可能非常有用的 channel 类似,我们不应该仅仅因为它们存在就到处使用它们 。
除了用于数据结构,泛型还有其他很好的应用场景 。当然,也有一些不好的用例,比如泛型日志器 。还有一些可以使用的解决方案,但相当丑陋,还有一些东西真的很丑 。
让我们分别看一个例子!
02 好的应用场景我真正梦想在 Go 中做的以及我认为我现在终于可以做的是 CRUD 端点的泛型提供程序:
type Model interface {ID() string}type DataProvider[MODEL Model] interface {FindByID(id string) (MODEL, error)List() ([]MODEL, error)Update(id string, model MODEL) errorInsert(model MODEL) errorDelete(id string) error}这是一个大接口,你可以根据具体用例的需要缩短它,但是,为了完整性起见,我们暂时就这么写 。
现在你可以定义一个使用 DataProvider 的 HTTP 处理程序:
type HTTPHandler[MODEL Model] struct {dataProvider DataProvider[MODEL]}func (h HTTPHandler[MODEL]) FindByID(rw http.ResponseWriter, req *http.Request) {// validate request hereid = // extract id heremodel, err := h.dataProvider.FindByID(id)if err != nil {// error handling herereturn}err = json.NewEncoder(rw).Encode(model)if err != nil {// error handling herereturn}}如你所见,我们可以为每个方法实现一次,然后我们就完成了 。我们甚至可以在事物的另一端创建一个客户端,我们只需要为基本方法实现一次 。
为什么我们在这里使用泛型而不是简单的我们已经定义的 Model 接口?
与在此处使用 Model 类型本身相比,泛型有一些优点:

  1. 使用泛型方法,DataProvider 根本不需要知道 Model,也不需要实现它 。它可以简单地提供非常强大的具体类型(但仍然可以为简单的用例抽象)
  2. 我们可以扩展这个解决方案并使用具体类型进行操作 。让我们看看插入或更新的验证器会是什么样子 。
type HTTPHandler[MODEL any] struct {dataProvider DataProvider[MODEL]InsertValidator func(new MODEL) errorUpdateValidator func(old MODEL, new MODEL) error}在这个验证器中是泛型方法的真正优势所在 。我们将解析 HTTP 请求,如果定义了自定义的 InsertValidator,那么我们可以使用它来验证模型是否检出,我们可以以类型安全的方式进行并使用具体模型:
type User struct {FirstName stringLastName string}func InsertValidator(u User) error {if u.FirstName == "" { ... }if u.LastName == "" { ... }}所以我们有一个泛型的处理器,我们可以用自定义回调来调整它,它直接为我们获取有效负载 。没有类型转换 。没有 map 。只有结构体本身!
03 不好的应用场景一起看一个泛型日志器的例子:
type GenericLogger[T any] interface {WithField(string, string) TInfo(string)}这本身还不是很有用 。有更简单的方法可以将键值字符串对添加到日志器,并且没有日志器(据我所知)实际实现此接口 。我们也不需要新的日志标准 。如果我们想使用 logrus[1],我们必须这样做:
type GenericLogger[T any, FIELD map[string]interface{}] interface{WithFields(M) TInfo(string)}如果我们添加自引用部分,这实际上可能由 logrus 日志器实现 。但是,让我们考虑在实际结构体中使用它,例如某种处理程序:
type MessageHandler[T GenericLogger[T], FIELD map[string]interface{}] struct {logger GenericLogger[T, FIELD]}为了在结构体中使用这个日志器,我们需要使我们的结构体泛型,这仅适用于日志器 。如果 MessageHandler 本身正在处理泛型消息,那将变成第三个类型参数!
到目前为止,甚至没有办法将其分配给具有泛型的变量 。所以,尽管我们可以用一个接口来表示这个日志器很棒,但我实际上建议不要这样做 。而我最喜欢的日志库 (zap[2]),由于其字段的性质,甚至无法用它来表示 。
04 丑的场景当我使用泛型时,我发现缺少对在方法中引入新泛型参数的支持 。虽然这可能有很好的理由,但它确实需要一些解决方法 。让我们想象一下我们想要将一个 map 简化为一个整数 。理想情况下,我们将通过使用返回新泛型参数的方法来完成此操作,然后我们可以简单地提供 map reduce 函数 。
那么,当我们仍然想以泛型方式缩小该 map 时,我们该怎么办?既然没有方法,那么让我们创建一个方法:
type GenericMap[KEY comparable, VALUE any] map[KEY]VALUEfunc (g GenericMap[KEY, VALUE]) Values() []VALUE {values := make([]VALUE, len(g))for _, v := range g {values = Append(values, v)}return values}func Reduce[KEY comparable, VALUE any, RETURN any](g GenericMap[KEY, VALUE], callback func(RETURN, KEY, VALUE "KEY comparable, VALUE any, RETURN any") RETURN) RETURN {var r RETURNfor k, v := range g {r = callback(r, k, v)}return r}


推荐阅读