go 规范:别名类型声明的文档周期限制

u0sqgete  于 6个月前  发布在  Go
关注(0)|答案(7)|浏览(60)

根据来自@rsc的原始类型别名设计文档,必须能够"展开"类型别名声明;对于type T = *T来说,如果不无限展开,这是不可能的。
该规范在这方面没有提及任何内容;并且在循环主题上通常模糊或保持沉默。

ar7v8xwq

ar7v8xwq1#

术语:我将“类型别名循环”用于诸如 type T = *T 之类的事物,并将“接口循环”用于诸如 type U interface { F() interface { U } } 之类的事物。
在这里,我看到了三个选项:

  1. 修订规范以禁止类型别名循环,但继续允许接口循环。
  2. 修订规范以禁止两者都存在。
  3. 决定允许两者都存在,并保持规范不变。
    我认为,在类型别名提案中,我们已经决定采用选项1(随后基本上实现了它),但我现在认为当时没有考虑到更大的范围。据我所知,类型别名循环引入的问题不应该比接口循环更难解决。例如,我们已经有不能扩展的类型:https://play.golang.org/p/8KQfNRbGwRV
    为了一致性,我倾向于支持选项2或3而不是选项1。
    选项2无疑是最简单的(除了如何在规范中解释它之外),但可能会破坏向后兼容性。
    我认为,如果我们对接受 #8082 感兴趣,那么选择3是必要的。
    --
    我认为,我们可以通过定义一个避免需要“展开”类型的类型序列化格式来追求选项3。例如,如果我们使用 $N 表示匿名循环(其中 N 衡量了类型字面量中“向外”的步骤数),那么一些示例如下:
  • type T = *T 可以描述为 type T = *$1
  • type U interface { F() interface { U } } 可以描述为 type U interface { F() $1 }(或者也许可以描述为 $2,如果我们将方法签名视为单独的类型)。
  • type V = map[V][]V 可以描述为 type V = map[$1][]$2

我们需要确保有一个规范Map。例如,[]V 可以写成 []map[$1]$2[]map[$1][]$2,但它必须始终是其中一个。(我倾向于前者;即,始终使用最短的类型描述符。)

cgfeq70w

cgfeq70w2#

@mdempsky 由于这种循环只能通过名称首先创建,为什么不保留名称呢?具体来说,这可能意味着在编译器中我们需要保留一个Alias类型节点(在使用之前总是先访问别名类型)。但它也允许更好的错误信息(如果有的话,可能会引用错误场景中使用的别名名称)。

ttvkxqim

ttvkxqim3#

我认为在编译时保留额外的类型信息是可以的,但我担心我们在链接时和运行时会做什么。
我们有很多方法依赖于相同的类型Map到相同的运行时类型描述符。例如,类型Assert、接口比较或使用 == 比较 reflect.Type 值。
所以如果我们在不同的包中有 type t1 = *t1type t2 = *t2,它们需要重用相同的运行时类型描述符。对于这个共享的运行时类型描述符,runtime.Type.String() 应该返回什么?
我们可以任意选择一个,但这对我来说似乎并不比使用合成占位符(如 $1$2 等)创建一个人工类型描述符更好。
或者,我们可能修改运行时代码,使其不依赖于类型标识与类型描述符唯一性之间的对应关系,但这似乎具有侵入性,并可能(轻微地,但负面地)影响甚至不使用匿名循环的 Go 程序。我甚至不确定如果我们去掉类型描述符唯一性,是否还能在 reflect.Type 上保持 == 的功能。

a1o7rhls

a1o7rhls4#

@mdempsky 很好的观点。编号方案似乎是一个更好的选择。我们不仅可以从左到右(写出来时)对引用的类型进行编号,还可以从左到右(写出来时)对引用的类型进行编号;这可能更容易理解。所以 T = *T 仍然是 *$1 (如前所述); V = map[V][]V 变为 map[$1][]$1 。这样做的好处是,给定的数字总是在给定的类型字符串中引用相同的类型。对于 TM ,T = *T; M = map[T]M 仍然是 *$1 ,而 map[*$2]$1M

oo7oh9g9

oo7oh9g95#

另一方面,从内到外的编号可能允许更简单的组合:在上面的最后一个示例中,我的外部类型(M)将以其内部类型的max + 1个数字开始编号(大致上说)。

xqnpmsa8

xqnpmsa86#

我看到了4种可能的编码方案。对于每一种,我都将以map[T][]T为例进行编码。

  1. map[*$1][]*$1。这是上面我建议的“向外”的方案。
  2. map[*$2][]$2。这是简单地从左到右对类型进行编号,参考第一次出现的相同类型的出现。(我认为这就是你建议的。)
  3. map[*$2][]*$4。这是2,但不允许除了打破循环之外的引用。
  4. map[*$2][]*$3。这是3,但每次我们“弹出”类型上下文时都会重置编号。(或者,可以将其视为1,但从根类型内部而不是从循环外部计数。)

我认为这些都可以工作,所以似乎取决于个人喜好选择使用哪一个。

我想说我们不应该使用2,因为这与我们处理非循环有向无环图(DAG)类型的方式不一致。例如,我不认为我们会将map[*int][]*int编码为map[*int][]$2。这使得正确实现变得更加困难(但并非不可能)。

我也想说我们不应该使用3,因为这使得人们很容易意外地实现2,它在大多数时候都能正常工作,但并非总是如此。(同样,虽然更复杂/微妙,但并非不可能。)

最后,我想说1比4更好,因为它具有这样一个属性:每次T发生时,都使用相同的描述(*$1),而4使用了两个不同的描述(*$2*$3)。

bwleehnv

bwleehnv7#

"inside out"编码类似于de Bruijn indices,它们同样保持术语局部化和可组合。(我不确定它们是否对应于上面的(1)或(4),但这并不重要。)

相关问题