Go维基:Go 2 泛型反馈
本页面旨在收集和整理关于 Go 2 的契约(泛型)设计草案的反馈。
语法原型实现可在以下链接找到:https://golang.ac.cn/cl/149638,该实现可在 Go 仓库的最新提交上进行补丁。
请将您的反馈发布到您的博客、Medium、GitHub Gists、邮件列表、Google文档等平台。然后请在此处链接。
随着反馈量增加,请随时根据反馈类型组织或重新组织本页面。
支持
-
Roger Peppe,“Go 契约用例:泛型 mgo”,2018年9月
-
Richard Fliam,“Go2 泛型允许您构造自然数”,2018年8月
补充(支持并提出修改意见)
-
Matt McCullough,“迈向清晰:Go 契约的语法变更”和“Go 契约的尖括号分隔符”,2020年5月
-
Gert Cuykens,“泛型语法与常规 Go 代码的完全分离”,2020年1月
-
Court Fowler,“一个懒惰程序员关于更新设计的思考”,2019年9月
-
Andrew Phillips,“作为契约的示例类型”,2019年8月
-
Alexey Nezhdanov,“一项语法简化提案”,2019年8月
-
Bryan Ford,“Go 2 泛型只用类型参数是否足够泛化?”,2019年7月
-
Tom Levy,“Go 2 泛型反馈”,2019年6月
-
Ole Bulbuk,“鉴于 Go 社区的变化,为什么 Go 契约不是个好主意”,2019年4月
-
Tony Mottaz,“Go 泛型类型和导入注入”,2019年3月
-
Gustavo Bittencourt,“契约仅用于泛型类型”,2019年3月
-
David Heuschmann,“使用括号表示类型参数列表的问题”,2019年2月
-
Gustavo Bittencourt,“带有方法的契约”,2019年2月
-
Chris Siebenmann,“Go 2 泛型:契约太巧妙了”,2018年11月
-
Chris Siebenmann,“Go 2 泛型:一种使契约对人来说更易读的方法”,2018年11月
-
Chris Siebenmann,“Go 2 泛型:接口不是类型约束的正确模型”,2018年11月
-
alanfo,“根据收到的反馈对 Go 泛型设计草案提出的修改建议”,2018年10月
-
Andy Balholm “枚举和结构化契约”,2018年10月
-
Burak Serdar “类型即契约”,2018年10月
-
Patrick Smith,“内置类型参数和用户定义类型参数的 Go 泛型”,2018年9月
-
Jacob Carlborg,“Go 2 草案 D 的更正”,2018年9月
-
alanfo,“一个简化的泛型约束系统”,2018年9月
-
Paul Borman,“简化语法”,2018年9月
-
mrwhythat,“Go 2 泛型草案笔记”,2018年9月
-
Roger Peppe,“运算符重载”,2018年9月
-
Peter McKenzie,“备选泛型语法”,2018年9月
-
Ted Singer,“语法的设计目标是帮助人类阅读”,2018年9月
-
alanfo,“对 Go 2 泛型设计草案的修订建议”,2018年9月
-
Dean Bassett,“如果我们要使用契约,允许对字符串使用一元 +”,2018年9月
-
Kevin Gillette,“关于 Go 2 泛型草案”,2018年9月
-
jimmy frasche,“不应允许嵌入类型参数”,2018年8月
-
Javier Zunzunegui,“编译泛型”,2018年8月
-
Liam Breck,“请勿破坏函数签名”,2018年8月
-
DeedleFake,“Go 2 设计草案反馈”,2018年8月
-
Roberto (empijei) Clapis,“难以阅读的语法”,2018年8月
-
Dominik Honnef,“我对 Go 泛型草案的思考”,2018年8月
反提案
-
dotaheor,“将泛型声明为带泛型参数的迷你包”,2020年8月
-
Beoran,“卫生宏”,2019年6月
-
Randy O’Reilly,“泛型原生类型”,2019年6月
-
Michal Štrba,“放弃限制类型”,2019年5月
-
Eric Miller,“使用 const 结构体字段的简单泛型”,2019年3月
-
dotaheor,“统一 Go 内置泛型和自定义泛型的方案”,2019年2月
-
Quentin Quaadgras,无语法变更,新增1个类型,新增1个内置函数,2018年12月
-
Andy Balholm,“契约和适配器”,2018年11月
-
Dean Bassett,“契约嵌入”,2018年10月
-
Patrick Smith,“带适配器的 Go 泛型”,2018年10月
-
Ian Denhardt,“Go 泛型:关于使用接口而非契约的具体提案。”,2018年10月
-
Arendtio “受接口启发的 Go 泛型”,2018年9月
-
Scott Cotton,“统一契约和接口的提案草案修改”(差异),2018年9月
-
ohir,“CGG,工匠 Go 泛型”,2018年9月
-
~~Dean Bassett,“使用接口代替契约”,2018年9月~~
我提出了第二个提案(“契约嵌入”),列在下面,解决了这个提案的问题 -
dotaheor,“将契约与代码结合,并将泛型视为具有多个输出的编译时调用”,2018年9月。(不定期更新)
-
Aleksei Pavliukov,“扩展 type 和 func 关键字”,2018年9月
-
Han Tuo,“泛型作为一种类型 – type T generic {int, float64}”,2018年9月
-
Nate Finch,“Go2 契约走得太远了”,2018年9月
-
Roger Peppe,“Go 契约作为类型结构体”,2018年9月
-
Axel Wagner,“废弃契约”,2018年9月
-
Matt Sherman “泛型作为内置类型类”,2018年9月
-
Roger Peppe,“修订后的泛型提案”,2018年9月
-
Steven Blenkinsop,“对 Go2 契约设计草案的回应 – 辅助类型”,2018年9月
-
Dave Cheney,“也许给 Go 添加泛型归根结底还是关于语法”,2018年9月
-
Christian Surlykke,“Go 泛型的约束”,2018年9月
-
go-nuts 邮件列表上的一些 Gopher,“统一接口和契约”,2018年8月
-
Roger Peppe,“Go 泛型反馈”,2018年8月
-
阮坤亮,“包级别泛型”,2018年8月
-
Emily Maier,“具体谈谈泛型”,2018年8月
反对意见
- Tokyo Gophers,“Go 2 设计草案反馈活动评论”,2018年10月
-
Jason Moiron,“Go2 泛型草案笔记”,2018年9月
-
Yoshiki Shibukawa,“泛型/契约提案反馈”,2018年9月
添加您的反馈
请按如下格式提交所有条目。
- 您的姓名,“标题”,月 年
为了更容易看到新的反馈。请创建一个 Gist。并通过将您的新条目放在类别列表的顶部来帮助按倒序排列列表。
快速评论
-
Chester Gould:这项提案唯一的问题在于,显式契约似乎只会使代码更加冗长,这与代码简洁可读的目标背道而驰。与其编写显式契约,不如将我们编写的实际代码作为一种“隐式契约”来使用,这样会更简单优雅。示例见此处。我承认这一点在此处中有所提及,但我不同意显式契约是解决问题的方案。在我看来,契约与接口提供的功能非常接近,因此应该扩展接口的行为,使其更接近契约,而不是在语言中添加一种全新的语句类型。
-
Izaak Weiss:很多讨论都集中在如何实现契约或类似的东西上。然而,大多数“有用的示例”并不需要契约;它们只需要参数多态性。编写类型安全的
Merge
或SortSlice
无需契约即可实现。对于更简单的契约,我们可以通过高阶函数来实现。通用哈希映射可以对带有Hash
方法的类型进行参数化,或者在构造时接受一个func(K) int64
函数,并用它来哈希其键。如果需要更多函数,可以声明包含这些函数的结构体作为伪契约,然后将它们传递给泛型函数。这使得 Go 的多态性变得简单、显式,并为将来在契约或其他机制方面的进一步创新留下了空间,同时现在即可实现泛型类型的大部分好处。 -
Christoph Hack:我刚看了 Alexandrescu 最近的讲座下一件大事。他说“概念是浪费时间”,并提出了一个完全不同、强大得多的方向(即使与今天 C++ 中一切可能的相比)。Go 已经拥有大多数所需特性,比如反射和测试一个类型是否实现了可选接口。唯一缺少的是代码生成。例如,
json.Marshal
通过使用反射工作得很好,但如果它也能(可选地)通过实现一个由编译器自动调用并运行常规 Go 代码的 Go 函数来生成代码,那么我们就拥有了一切。起初这可能听起来很疯狂,示例代码可能显得冗长,但我认为 Alexandrescu 的观点很有道理。只需想想 gqlgen 与其他基于反射的 graphql 库之间的对比即可。请观看他的讲座! -
Bodie Solomon:我发现泛型设计有点令人困惑和模糊。请考虑整合一些来自Zig 美妙的 comptime 函数的概念!Go 2 泛型设计很巧妙,但我感觉它违背了 Go 传统上简单运行时语义与简单语法之间的紧密耦合。此外,Go 的一个最大问题是无法摆脱 GC 和运行时,这使得它无法在我可能希望使用它的任何地方成为一个有力的竞争者。我强烈希望 Go 2 能引入仅限于编译时的泛型,这样我就能在不需要动态接口的地方可靠地避免使用它们,而无需依赖代码生成。不幸的是,这似乎将由编译器在没有我的输入的情况下决定。请至少考虑让用户能够将泛型限制为仅在编译时解析,也许可以作为契约的一个属性,拒绝编译动态类型以满足契约。
-
Dag Sverre Seljebotn:C++ 有一个大问题,就是人们滥用元编程(“泛型”)来进行编译时元编程。我真希望 Go 能走 Julia 的道路,Julia 提供了卫生宏。即使严格限制在编译时,没有运行时代码生成,这至少可以避免我们在 C++ 世界中看到的所有源自其模板系统的糟糕倾向。您可以用泛型做的事情,通常也可以用宏来实现(例如,
SortSliceOfInts = MakeSliceSorterFunctionMacro!(int)
可以生成一个新函数来对整数切片进行排序)。链接:https://docs.julia-lang.cn/en/v0.6.1/manual/metaprogramming/ -
Maxwell Corbin:在讨论和开放问题部分提出的问题都可以通过在包级别而不是函数或类型级别定义泛型来避免。原因很简单:类型可以引用自身,但包不能导入自身;虽然有很多方法可以算法生成更多类型签名,但导入语句却不行。这样的语法快速示例如下:
\\ list package list[T] type T interface{} type List struct { Val T Next *List } // main package main import ( il "list"[int] sl "list"[string] ) var iList = il.List{3} var sList = sl.List{"hello"} // etc...
示例中的语法可能不必要地冗长,但重点在于博客文章中那些不幸的代码示例甚至不是合法的结构。包级别泛型避免了元编程最恶劣的问题,同时保留了其大部分实用性。
-
Andrew Gwozdziewycz:使用
contract
这个词让我犹豫,因为它与契约式设计中的“契约”概念重叠。尽管泛型用例与 DbC 中的“契约”有些许相似之处,但概念却截然不同。由于“契约”在计算机科学中是一个既定的概念,我认为使用像behavior
或trait
这样的不同名称会减少很多困惑。设计文档也提出了为什么使用interface
不理想的原因,然而,Go 的契约机制看起来像是接口的明显扩展,不应如此迅速地忽略……如果能做到,interface setter(x T) { x.Set(string) error }
和interface addable(x T, y U) { x + y }
读起来和理解起来似乎非常自然。- Russell Johnston:同意合并契约和接口将是很棒的。解决运算符命名问题的另一种方法是为运算符提供一些标准接口,这些接口的主体在普通 Go 代码中无法表达。例如,一个标准
Multipliable
接口将允许*
和*=
运算符,而一个标准Comparable
接口将允许==
,!=
,<
,<=
,>=
和>
。为了表达涉及多种类型的运算符,这些接口本身可能需要类型参数,例如:type Multipliable(s Self /* this exists implicitly on all interfaces */, t Other) interface { /* provided by the language */ }
。然后用户编写的接口/契约可以使用这些基于标准标识符的名称,巧妙地回避了设计文档中提到的语法和类型问题。 - Roberto (empijei) Clapis:我同意这一点,也同意应该更清楚地界定在哪里使用接口、在哪里使用契约。统一两者将是很棒的,因为它们试图解决重叠的问题。
- Kurnia D Win:我认为
constraint
比contract
是更好的关键字。我个人喜欢type addable constraint(x T, y U) { x + y }
这种方式,而不是与接口合并。
- Russell Johnston:同意合并契约和接口将是很棒的。解决运算符命名问题的另一种方法是为运算符提供一些标准接口,这些接口的主体在普通 Go 代码中无法表达。例如,一个标准
-
Hajime Hoshi:我感觉这项所谓的提案对于我们在https://go.googlesource.com/proposal/+/master/design/go2draft-generics-overview.md 列出的我们想要解决的问题来说,规模太大了。我担心这项特性会被滥用并降低代码的可读性。抱歉如果我理解有误,但提案没有提及任何关于
go generate
的内容。go generate
不足以解决这些问题吗? -
Stephen Rowles:我觉得方法语法很难解析,作为人类读者,对于类型部分,使用不同类型的括号可能更清晰,例如:我也这么认为 👍 +1。又一个 👍 +1 (Pasha Osipyants)。
func Sum<type T Addable>(x []T) T { var total T for _, v := range x { total += v } return total }
-
yesuu:在这个例子中,将
T
视为参数名,将type
视为参数类型。显然,将type
放在后面更合理,契约跟在type
后面,就像chan int
。func Sum(T type Addable)(x []T) T
-
Seebs:反馈有点长,不便内联,2018年8月。摘要主要内容是:“我希望有一种方法可以为两种类型中的每一种指定一个契约,而不是为这两种类型指定一个共享契约”,以及“我更喜欢使用
map[T1]T2
而不是t1var == t1var
作为“T1 必须是允许的映射键”的标准形式。 -
Seebs:如果契约只是类型参数化函数呢?。(2018年9月1日)
-
Sean Quinlan:我觉得契约语法相当令人困惑。对于一个应该精确定义所需内容并作为 API 文档一部分的东西来说,它可能包含各种不影响契约的冗余内容。此外,引用设计文档中的话:“我们不需要解释契约主体中出现的每个语句的含义”。这似乎与我对契约的要求恰恰相反。可以将函数体复制到契约中并使其工作,这在我看来像是一个 bug,而不是一个特性。就我个人而言,我更倾向于一种统一接口和契约的模型。接口感觉更接近我所期望的契约外观,并且存在很多重叠。许多契约也可能是接口,这似乎很可能?
-
Nodir Turakulov:请详细说明
像 container/list 和 container/ring 这样的包,以及像 sync.Map 这样的类型,将被更新为编译时类型安全。
以及
math 包将被扩展,为所有数值类型提供一组简单的标准算法,例如非常流行的 Min 和 Max 函数。
或者最好增加一节关于现有类型/函数过渡/迁移到使用类型多态性的内容。据我所知(FWIU),向现有类型/函数添加类型参数很可能会破坏使用该类型/函数的现有程序。
math.Max
将如何精确更改?是否打算进行向后不兼容的更改,并编写工具自动将代码转换为 Go2?对于提供当前使用interface{}
的函数和类型的其他库的作者,有什么一般性建议?是否考虑了类型参数的默认值?例如,math.Max
的类型参数默认值为float64
,而"container/list".List
的类型参数默认值为interface{}
-
Ward Harold:即使只是为了完整性,Modula-3 的泛型设计也应该纳入其他语言的设计一节。Modula-3 是一门优美的语言,可惜在错误的时间被引入。
- Matt Holiday:同上,提到Alphard语言,它与 CLU 大约同时开发,并影响了 Ada 设计。关于收集整理的各种论文,请参阅 Mary Shaw 编辑的《Alphard: Form and Content》,Springer 1991 出版。Alphard 和 Ada 是我接触泛型编程的入门。Go 能否在等待了40年后最终交付契约方面超越 C++?
-
Ole Begemann:您在泛型概述页面中写道:“Swift 在 Swift 4 中添加了泛型,于 2017 年发布。”这是不准确的。Swift 自 2014 年首次公开发布以来就有了泛型。证据(众多示例中的一个):WWDC 2014 上一次苹果开发者关于 Swift 的讲座记录,其中详细讨论了 Swift 的泛型特性。
这也不正确:“
Equatable
似乎是 Swift 的内置类型,无法以其他方式定义。”Equatable
协议是在 Swift 标准库中定义的,但它没有什么特别之处。完全可以在“普通”代码中定义相同的功能。 -
Kevin Gillette:关于“契约”草案的更正,截至 2018年8月30日
check.Convert(int, interface{})(0, 0)
的一个实例应该改为check.Convert(int, interface{})(0)
,或者解释为什么函数需要接受两个零而不是一个。 -
Adam Ierymenko:我有一个在 Go 中进行有限运算符重载的想法,这可能会使这项提案对数值计算代码更有用。它比较长,所以我把它放到了这里的 Gist 中。
- DeedleFake:我完全同意反对运算符重载的论点,并且总体上很高兴 Go 没有它,但我同时认为,目前的设计草案最大的问题在于无法通过契约区分
a == b
和a.Equals(b)
。这意味着对于相当多的事情,您仍然需要编写多个函数。例如,尝试编写一个二叉树。您应该使用带有t < t
还是t.Less(t)
的契约?对于求和函数,您应该使用t + t
还是t.Plus(t)
?不过,我绝对想要一个不涉及运算符重载的解决方案。也许可以指定一个适配器,它基本上表示如果一个满足契约 A 但不满足契约 B 的类型 T 被用于受契约 B 约束的参数,则对其应用此适配器以使其满足契约 B
。例如,契约 B 可能需要一个Plus()
方法,而契约 A 需要使用+
运算符,因此适配器会自动将用户指定的Plus()
方法附加到T
类型上,在其符合该契约的使用期间有效。- 一种可能适用于这项提案的方法是引入一个内置函数
equal(a, b)
,如果a.Equals(b)
存在则使用它,否则使用a == b
,如果类型不可比较则编译失败(其他运算符同理)。这太奇怪了,不太值得认真考虑,但它将适用于契约,并允许在不引入运算符重载的情况下规避具有运算符的类型和不能的类型之间的不对称性 —jimmyfrasche - 另一个想法是引入可显式重载的运算符:
a + b
不可重载,但a [+] b
可以重载。对于基本类型,它将使用普通的 + 运算符,但如果对象存在相应的Operator+()
等方法,则将使用这些方法。我真的认为,如果泛型没有某种合理的运算符重载形式或类似功能,其用处会大打折扣,甚至不如不做。-Adam Ierymenko(原帖作者)
- 一种可能适用于这项提案的方法是引入一个内置函数
- Ian Denhardt:DeedleFake 很好地阐述了没有运算符重载的问题。我认为那些涉及使重载“显眼”的提案是错误的想法;相反,我们应该限制哪些运算符可以被重载,只允许满足以下条件的运算符:
- 运算符的语义可以理解为方法调用。大多数数字上的运算符都符合这一条件;从 int32、uint64 等类型我们知道,
big.Add
仍然是加法。不符合这一条件的运算符示例是&&
和||
;它们是短路运算符,任何函数或方法都无法复制其行为。从根本上讲,无论如何看待它们,它们都不是方法,也不应该被方法重载。我认为运算符重载之所以名声不好,部分原因在于 C++ 允许重载一切,包括像逗号运算符这样疯狂的东西。 - 应该有明确的用例来重载它们。再次强调,算术运算符符合这一条件,以及
<
及相关运算符。指针解引用符合第一个条件,但我很难想出对于“其他类型的指针”来说,哪些用例是实际的好主意。在 C++ 中它们稍微合理一些,但垃圾回收的指针基本上已经满足了您的需求。 - 运算符的正常含义应该易于理解。例如,指针是产生 bug 的复杂源头,如果
*foo
可能会做除了从内存地址读取以外的事情,那么会让本已困难的调试会话变得更加艰难。另一方面,+
可能是调用big.Add
的可能性相对独立,不太可能引起大的困惑。 - 最后,标准库必须树立一个好榜样;例如,重载
+
的方法在概念上应该是加法。C++ 在这里走错了路,它允许将os.Stdout.ShiftLeft("Hello, World!")
这样的行为定义为重载。
- 运算符的语义可以理解为方法调用。大多数数字上的运算符都符合这一条件;从 int32、uint64 等类型我们知道,
- DeedleFake:我完全同意反对运算符重载的论点,并且总体上很高兴 Go 没有它,但我同时认为,目前的设计草案最大的问题在于无法通过契约区分
-
Eltjon Metko:在函数参数中的类型标识符之后指定契约如何?这样就可以推断出 T 是什么,并且我们可以消除第一组括号。
func Sum(x []T:Addable) T { var total T for _, v := range x { total += v } return total }
-
Tristan Colgate-McFarlane:经过一番反复思考,我基本上赞成目前提案的现状。契约的有限语法可能更可取,但我认为它应该允许引用特定字段(而不仅仅是像一些人提议的那样引用方法)。如果能做些什么使兼容的接口和契约之间的互用更容易,那也将是很棒的(尽管我认为可能不需要额外的规范)。最后,我认为值得考虑弃用接口类型。虽然这一步很激进,但契约本质上也允许指定行为。任何限制此功能的契约约束(例如引用包内的其他类型)都应该被取消。契约似乎是接口的严格超集,我通常反对存在两个重叠的特性。还应该考虑开发一个辅助编写接口的工具。
-
Patrick Smith: 我们可能会考虑在泛型类型上定义方法时要求使用 type 关键字。这会使代码稍微更冗长,但更清晰、更一致(现在类型参数总是在 type 关键字前面)。
func (x Foo(type T)) Bar()
-
Patrick Smith: 在这个例子中,是
Foo(T)
嵌入在Bar(T)
中,还是Bar(T)
有一个名为Foo
的方法?type Foo(type T) interface {} type Bar(type T) interface { Foo(T) }
-
Xingtao Zhao: 提案中有太多的圆括号。提案中提到,“[]”在某些情况下会引起歧义。然而,如果我们使用 [type T, S contract],就不会再有歧义了。
-
Dave Cheney: 早前的类型函数提案表明,类型声明可以支持参数。如果这是正确的,那么提议的契约声明可以从
contract stringer(x T) { var s string = x.String() }
到
type stringer(x T) contract { var s string = x.String() }
这支持了 Roger 的观察,即契约是接口的超集。
type stringer(x T) contract { ... }
引入了一个新的契约类型,就像type stringer interface { ... }
引入一个新的接口类型一样。- jimmyfrasche: 然而,契约不是一个类型。你不能拥有一个值是
stringer
。你可以拥有一个其类型是stringer
的值。它是一个元类型。类型是值上的一种谓词。你问编译器“这个值是string
吗”,它回答是(允许继续编译)或否(停止并告诉你出了什么问题)。契约是类型向量上的谓词。你问编译器两个问题:这些类型满足这个契约吗?然后:这些值满足这些类型吗?接口通过存储一个 (type, value) 对来模糊这些界限,前提是该类型具有适当的方法。它同时是一个类型和一个元类型。任何不使用接口作为元类型的泛型系统都不可避免地会包含一个接口的超集。虽然完全可以定义一个专门使用接口作为元类型的泛型系统,但这确实意味着失去了编写使用接口无法表达的事物(例如运算符)的泛型函数的能力。你必须将对类型提出的问题限制在其方法集上。(我对此没意见)。
- jimmyfrasche: 然而,契约不是一个类型。你不能拥有一个值是
-
btj: 草案设计文档的“其他语言中的设计”部分遗漏了两个非常重要的条目:带有类型类的 Haskell,以及带有隐式参数的 Scala。
-
iamgoroot: 难道更自然的做法不是提供更好的类型别名支持,并让用户将泛型作为可选功能吗?这样也不需要很多语法。
type Key _
type Value _
type IntStringHolder Holder<Key:int, Value:string>
type Holder struct {
K Key
V Value
}
func (h *Holder) Set(k Key, v Value) {
h.K = k
h.V = v
}
func main() {
v:= IntStringHolder{}
v.Set(7,"Lucky")
}
-
antoniomo: 虽然草案清楚地解释了为什么
F<T>
、F[T]
和非 ASCII(这里无法输入)F<<T>>
被弃用,但感觉F{T}
会比有时连续出现三个的()
更易读,同时也不会因为在那种情况下无法打开块而使解析器面临无限前瞻的复杂性。 -
aprice2704: 我非常不喜欢使用常规圆括号
(
的想法,两个字符的序列会导致无限前瞻带来的编译器开销吗?<|
和|>
如何?它们可行吗?它们的优点是与(
截然不同,在 ascii 中有一些视觉意义,并且在我用于 VSCode 的 ‘Fira Code’ 字体(强烈推荐)中,有连字将它们渲染成小的右指或左指三角形。 -
leaxoy: 首先,很抱歉编辑了页面页脚,但我无法删除页脚内容。这是我的观点:很多
(
&)
让 Go 看起来很乱,像其他语言那样使用<
&>
更好,对来自其他语言的人也更友好。 -
Hajime Hoshi: 我完全同意 aprice2704 关于语法问题的担忧。例如,使用
[[
/]]
可以吗?
此内容是 Go Wiki 的一部分。