解析类型参数

解析,类型,参数 · 浏览次数 : 6

小编点评

**Go 语法需要波浪符** 由于Go语言是类型安全的,需要在类型声明中明确类型参数的类型。波浪符可以用于表示类型参数的类型。 **波浪符的作用** 波浪符可以用于以下两种目的: 1. 表示类型参数的类型。 2. 表示类型参数的元素类型。 **示例** ```go // 带波浪符的类型参数 s := Clone[MySlice, string](ms) // 带波浪符的元素类型 t := Clone[MySlice, MySlice](ms) ``` **结论** Go 语法需要波浪符用于表示类型参数的类型。波浪符可以用于表示类型参数的类型参数的类型或元素类型。

正文

原文在这里

由 Ian Lance Taylor 发布于2023年9月26日

slices 包函数签名

slices.Clone 函数很简单:它返回一个任意类型切片的副本:

func Clone[S ~[]E, E any](s S) S {
    return append(s[:0:0], s...)
}

这个方法有效的原因是:向容量为零的切片追加元素将分配一个新的底层数组。函数体的长度最终比函数签名的长度要短,函数体短是一方面原因,函数签名长是另一方面原因。在本博客文章中,我们将解释为什么函数签名被写成这样。

Simple Clone

我们将从编写一个简单的通用 Clone 函数开始。这不是 slices 包中的函数。我们希望接受任何元素类型的切片,并返回一个新的切片:

func Clone1[E any](s []E) []E {
    // body omitted
}

这个通用函数Clone1有一个名为E的类型参数。它接受一个参数 s,该参数是类型为E的切片,并返回相同类型的切片。这个签名对于熟悉 Go 中泛型的人来说是直观的。

然而,存在一个问题。在 Go 中,命名切片类型并不常见,但人们确实在使用它们。

// MySlice is a slice of strings with a special String method.
type MySlice []string

// String returns the printable version of a MySlice value.
func (s MySlice) String() string {
    return strings.Join(s, "+")
}

假设我们想复制一个 MySlice,然后获取可打印版本,但要按照字符串的排序顺序排列:

func PrintSorted(ms MySlice) string {
    c := Clone1(ms)
    slices.Sort(c)
    return c.String() // FAILS TO COMPILE
}

很不幸,上面的代码并不能成功运行,编译器报错信息如下:

c.String undefined (type []string has no field or method String)

如果我们手动用类型参数替换类型参数来实例化 Clone1,我们可以看到问题所在:

func InstantiatedClone1(s []string) []string

Go的赋值规则允许我们将类型为 MySlice 的值传递给类型为 []string 的参数,因此调用 Clone1 是可以的。但是 Clone1 将返回类型为 []string 的值,而不是类型为 MySlice 的值。类型 []string 没有 String 方法,因此编译器会报错。

Flexible Clone

要解决这个问题,我们需要编写一个返回与其参数相同类型的Clone版本。如果我们能做到这一点,那么当我们使用类型MySlice的值调用Clone时,它将返回类型MySlice的结果。

结果如下:

func Clone2[S ?](s S) S // INVALID

这个Clone2函数返回与其参数相同类型的值。

这里我把约束写为了?,但这只是一个占位符。要使它工作,我们需要写一个能让我们编写函数体的约束。对于Clone1,我们可以只使用any进行约束。但对于Clone2,这样做不起作用:我们想要要求s是一个切片类型。

由于我们知道我们想要一个切片,切片的约束必须是一个切片。我们不关心切片元素类型是什么,所以我们就像在Clone1中一样将其命名为E

func Clone3[S []E](s S) S // INVALID

这仍然是无效的,因为我们还没有声明E。类型参数E的类型参数可以是任何类型,这意味着它本身也必须是一个类型参数。由于它可以是任何类型,所以它的约束是any

func Clone4[S []E, E any](s S) S

这已经接近了,至少它会编译通过,但我们还没有完全解决问题。如果我们编译这个版本,当我们调用Clone4(ms)时会出现错误。

MySlice does not satisfy []string (possibly missing ~ for []string in []string)

编译器告诉我们,我们不能将类型参数MySlice用于类型参数S,因为MySlice不满足约束[]E。这是因为[]E作为约束仅允许切片类型字面量,如[]string。它不允许像MySlice这样的命名类型。

基础类型的约束

根据错误提示,答案是加一个波浪线(~)。

func Clone5[S ~[]E, E any](s S) S

再次重申,编写类型参数和约束 [S []E, E any] 意味着S的类型参数可以是任何未命名的切片类型,但不能是定义为切片文字的命名类型。编写 [S ~[]E, E any],带有一个波浪线,意味着 S 的类型参数可以是底层类型为切片的任何类型。

对于任何命名类型 type T1 T2T1的底层类型是T2的底层类型。预声明类型如 int 或类型文字如 []string 的底层类型就是它们自身。有关详细信息,请参阅语言规范。在我们的示例中,MySlice的底层类型是[]string

由于MySlice的底层类型是切片,因此我们可以将类型为MySlice的参数传递给Clone5。正如您可能已经注意到的,Clone5的签名与slices.Clone的签名相同。我们终于达到了我们想要的目标。

在继续之前,让我们讨论一下为什么 Go 语法需要一个波浪符(~)。看起来我们总是希望允许传递MySlice,那么为什么不将其作为默认值呢?或者,如果我们需要支持精确匹配,为什么不反过来,使约束[]E允许命名类型,而约束,比如=[]E,只允许切片类型文字?

为了解释这一点,让我们首先观察一下[T ~MySlice]这样的类型参数列表是没有意义的。这是因为MySlice不是任何其他类型的底层类型。例如,如果我们有一个定义如type MySlice2 MySlice的定义,MySlice2的底层类型是[]string,而不是MySlice。因此,[T ~MySlice]要么不允许任何类型,要么与[T MySlice]相同,只匹配MySlice。无论哪种方式,[T ~MySlice]都是没有用的。为了避免这种混淆,语言禁止[T ~MySlice],并且编译器会产生错误,例如

invalid use of ~ (underlying type of MySlice is []string)

如果 Go 不需要波浪符,让[S []E]匹配任何底层类型是[]E的类型,那么我们将不得不定义[S MySlice]的含义。

我们可以禁止[S MySlice],或者我们可以说[S MySlice]只匹配MySlice,但无论哪种方法都会遇到与预声明类型的问题。预声明类型,比如int,其底层类型是它自身。我们希望允许人们编写接受底层类型为int的任何类型参数的约束。在今天的语言中,他们可以通过编写[T ~int]来实现这一点。如果我们不需要波浪符,我们仍然需要一种方式来表示“任何底层类型是int的类型”。自然的表达方式将是[T int]。这将意味着[T MySlice][T int]的行为将不同,尽管它们看起来非常相似。

我们也可以说[S MySlice]匹配任何底层类型为MySlice底层类型的类型,但这将使[S MySlice]变得不必要和令人困惑。

我们认为有必要要求使用波浪符,明确何时匹配底层类型而不是类型本身。

类型接口

现在我们已经解释了slices.Clone的签名,让我们看看如何通过类型推断来简化实际使用slices.Clone。请记住,Clone的签名是

func Clone[S ~[]E, E any](s S) S

对于slices.Clone的调用将传递一个切片给参数s。简单的类型推断将允许编译器推断类型参数S的类型参数是传递给Clone的切片的类型。类型推断还足够强大,可以看出类型参数E的类型参数是传递给S的类型参数的元素类型。

这意味着我们可以写成

c := Clone(ms)

而不必写成

c := Clone[MySlice, string](ms)

如果我们引用Clone而不调用它,我们必须为S指定一个类型参数,因为编译器没有可以用来推断它的信息。幸运的是,在这种情况下,类型推断能够从S的参数中推断出类型参数E的类型参数,因此我们不必单独指定它。

也就是说,我们可以写成

myClone := Clone[MySlice]

而不必写成

myClone := Clone[MySlice, string]

解析类型参数

我们在这里使用的一般技术是,通过使用另一个类型参数E定义一个类型参数S,这是一种在通用函数签名中拆解类型的方法。通过拆解类型,我们可以命名并约束类型的所有方面。

例如,这是maps.Clone的签名。

func Clone[M ~map[K]V, K comparable, V any](m M) M

slices.Clone一样,我们使用一个类型参数来表示参数m的类型,然后使用另外两个类型参数KV来拆解类型。

maps.Clone中,我们约束K必须是可比较的,因为这是映射键类型所要求的。我们可以按照自己的喜好约束组件类型。

func WithStrings[S ~[]E, E interface { String() string }](s S) (S, []string)

这表示WithStrings的参数必须是一个切片类型,其元素类型必须具有String方法。

由于所有的 Go 类型都可以由组件类型构建而来,因此我们始终可以使用类型参数来拆解这些类型并根据需要对其进行约束。


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


与解析类型参数相似的内容:

解析类型参数

原文在这里。 由 Ian Lance Taylor 发布于2023年9月26日 slices 包函数签名 slices.Clone 函数很简单:它返回一个任意类型切片的副本: func Clone[S ~[]E, E any](s S) S { return append(s[:0:0], s...

[转帖]SpringBoot配置SSL 坑点总结【密码验证失败、连接不安全】

文章目录 前言1.证书绑定问题2.证书和密码不匹配3.yaml配置文件问题3.1 解密类型和证书类型是相关的3.2 配置文件参数混淆 后记 前言 在SpringBoot服务中配置ssl,无非就是下载证书设置一下配置文件的问题,这里主要记录我在配置的过程中遇到的坑点。 如果是新手上道的话建议结合其他的

Python初学者友好丨详解参数传递类型

摘要: 本文清晰地解释了Python中的不同参数传递类型,并提供了示例代码来说明每种类型的用法。对于初学者或不清楚Python传参的读者们来说是非常有益的,文中提供了足够的信息来理解和使用Python中的函数参数传递。 本文分享自华为云社区《提升Python函数调用灵活性:参数传递类型详解》,作者:

[4]自定义Lua解析器管理器-------演化脚本V0.7

使用自定义委托通过tolua来调用多返回值和长参数类型的函数。 防踩坑指南,使用自定义委托需要将委托类型添加到CustomSettings中。

【转帖】71.常用的显示GC日志的参数、GC日志分析、日志分析工具的使用

目录 1.常用的显示GC日志的参数2.图解垃圾`GC`日志(重要)3.日志分析工具的使用 1.常用的显示GC日志的参数 解释: 日志中,GC和Full GC表示的是GC的类型。GC只在新生代进行,Full GC包括新生代和老年代、方法区。 Allocation Failure:GC发生的原因,一般新

简单易懂的JSON框架

分享一个由本人编写的JSON框架。 JSON反序列化使用递归方式来解析JSON字符串,不使用任何第三方JAR包,只使用JAVA的反射来创建对象(必须要有无参构造器),赋值,编写反射缓存来提升性能。支持复杂的泛型类型,数组类型等所有类型。(不支持高版本JDK1.8以上的日期类型,如LocalDate,

[转帖]Linux——Shell脚本参数传递的2种方法

https://www.cnblogs.com/caoweixiong/p/12334418.html 前言 平时会遇到很多脚本都有参数选项,类似: ./test.sh -f config.conf -v --prefix=/home 这种脚本怎么写呢? 一、Shell 特殊参数解释 首先来看几个特

C#中关于 object,dynamic 一点使用心得

首先说一下使用场景 WebAPI接口入参使用 object和 dynamic 后续解析和处理 1.object和dynamic 区别 在.NET中,object和dynamic也有一些区别: object:object是.NET中的顶级类,所有类都是object的子类。在C#中,您可以使用objec

CSS3新特性值逻辑选择器

1. :is 解释::is() CSS伪类函数将选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。 例如 对于多个不同父容器的同个子元素的一些共性样式设置,传统写法如下 header p:hover, main p:hover, footer p:hover { color: red;

《系列一》-- 2、XmlBeanFactory 的类图介绍.md

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。 Spring源码阅读系列--全局目录.md 引子 1、容器最基本使用.md 系列1 - bean 标签解析: 2、XmlBeanFactory