研究!rsc公司

关于编程的想法和链接,通过

RSS(RSS)

一般困境
发布于2009年12月3日星期四。

通用数据结构(向量、队列、地图、树等)似乎是热门话题如果你正在评估一门新语言。其中一个最常见的问题我们已经谈过了Go是泛型所在的位置。似乎有三种基本的泛型方法:

  1. (C方法。)把它们去掉。
    这会减慢程序员的速度。
    但它并没有增加语言的复杂性。
  2. (C++方法)编译时专门化或宏扩展。
    这会减慢编译速度。
    它生成了大量代码它是多余的,需要一个好的链接器来消除重复的副本。个别专业化可能有效,但由于指令缓存使用不当,整个程序都会受到影响。我听说过有文本段的简单库通过修改将兆字节缩小到几十千字节或消除模板的使用。
  3. (Java方法)隐式地将所有内容装箱。
    这会减慢执行速度。
    与实施方式相比C程序员或C++编译器会编写生成后,Java代码更小,但效率更低时间和空间,因为所有的隐式装箱和拆箱。字节向量每字节使用的字节明显多于一个。试图隐藏装箱和拆箱也可能使类型系统复杂化。另一方面,它可能会更好地利用指令缓存,可以写入字节向量分别进行。

一般的困境是:你想要吗缓慢的程序员、缓慢的编译器和膨胀的二进制文件,还是执行速度慢?

我很乐意了解实施情况以某种方式避免了这三种情况糟糕的结果,尤其是如果写得好描述(论文、博客文章等)困难以及为什么这是一个好方法。我也有兴趣看到好的书面描述尝试其中一种方法以及出现了什么问题(或右)。

请留下带有指针的评论。谢谢。

(评论最初通过Blogger发布。)

  • 尤库 (2009年12月3日上午9:54)C#实现怎么样?它允许您使用基本体类型。

  • 巴里·凯利 (2009年12月3日上午10:00)你的方法列表中的巨大漏洞是。NET:具体化泛型,但使用面向引用类型的语言。

    C++之所以受到影响,是因为它对面向值的编程有很多支持,使用了“聪明”的技巧,如复制构造函数、赋值运算符、隐式转换等。GC的缺乏鼓励人们使用几乎是“非十分优秀”的智能指针,而在集合中存储智能指针意味着每个集合都需要调用适当的复制构造函数、析构函数等进行内部操作。这使得共享代码变得很困难。

    .NET已经具体化了泛型,但所有具有引用类型的泛型实例化都在幕后使用相同的实际实例化,使用隐藏类型“System.__Canon”作为类型参数。这需要在特定位置的实际类型参数列表中进行走私,例如泛型方法调用的隐藏参数、不同实例化类型的类型描述符中的字段等。

    .NET泛型也在运行时实例化,这意味着它可以跨多个动态链接库共享实例化。在预编译环境中,这有点难以实现;通过一些间接操作,您可能能够获得大部分好处,因为只有一个实例化处于活动状态并处于缓存中,而其他实例化则处于冷态而不被分页。

  • 安德烈·杜克 (2009年12月3日上午10:15)http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx谈到了。Net Generic实现更详细。

  • 保罗·斯奈利 (2009年12月3日上午10:51)我还想看看BitC的多实例系统。事实上,一般来说,我会尽可能多地从BitC中获得灵感,而不会牺牲BitC不一定共享的任何特定目标(例如,盲目的编译速度)。

  • 纽特0311 (2009年12月3日上午11:11)超级编译器可以解决这个问题,但要使它们有效工作,需要在编译器研究方面取得一些重大进展。这是学术CS中少数有用的领域之一。

  • 信用证 (2009年12月3日上午11:19)C#不允许使用“int”或其他基本类型作为泛型的参数。

    我认为C++选择了“最不坏”的解决方案——你只需编译一次应用程序,就可以多次运行它。

  • 米哈伊尔 (2009年12月3日上午11:46)@抄送:不确定我是否正确理解您的意思,但使用C#您可以执行以下操作:

    //声明int类型的列表。
    GenericList<int>list1=新GenericList();

  • 大卫 (2009年12月3日上午11:52)是的,例如Action[int],它是一种表示函数的类型,该函数接受整数参数,但不返回任何值。

  • 霍德斯博士 (2009年12月3日上午11:53)这篇论文可能值得一看:

    http://www.osl.iu.edu/publications/prints/2003/comparing_generic_programming03.pdf

    摘要中的片段:。。。全面比较六种编程语言中的泛型:C++、Standard ML、Haskell、Eiffel、Java(及其建议的泛型扩展)和Generic C#。。。

    虽然付费墙后面有一份新的论文(2007年)。

    粗略地看一下第3页的表,就会发现haskell的特性集相对于其他列出的语言来说是多么全面。

    IMHO,可组合类型类非常适合Go的正交哲学。

  • 安德鲁德克尔 (2009年12月3日下午12:55)该论文基于C#中的泛型原型,在最终版本完成之前-隐式类型被提到是缺乏的,而最终版本支持它(例如)。

  • 大卫·安徒生 (2009年12月3日下午1:28)嗨,Russ,不是对你的问题发表评论,而是一个元观点:你的帖子没有解决容器是否静态类型安全的问题。虽然可以想象这与代码的工作方式是正交的(例如,静态类型检查,然后使用相同的impl进行box/unbox),但实际上,在大多数情况下,泛型也提供类型安全,而装箱/unbox方法只提供运行时类型检查。实际上,问题似乎是:编译器速度慢,二进制文件臃肿,但静态类型更安全。当然,这仍然是一个问题。

  • 里沃鲁斯 (2009年12月3日下午1:53)[由两部分组成的帖子]

    Go绝对应该尝试并支持Stepanov设想的泛型编程范式,这最接近于用C++模板、重载和专门化来实现(这就是为什么STL首先在C++中实现的原因)。他最近出版的《编程元素》(Elements of Programming)一书在通读泛型编程方面做得很好,我强烈建议所有程序员阅读,无论他们的语言背景如何。

    这并不是说Go需要C++意义上的模板,它所需要的只是类型函数,这是一种指定真的泛型编程概念(包括相关类型和C++0x concept_maps等,而不仅仅是Go接口的基本名称/函数签名匹配),以及基于概念细化和特定概念模型专门化/重载算法的方法。

    请记住,C++关于通用算法和数据结构的模板比“快速”更有优势,它们可以在编译时进行调度,并且能够在编译时处理类型/概念信息,这意味着在程序运行之前捕获更多错误。此外,由于您为编译时带来了更多的关系,某些复杂的操作(如Java或C#世界中使用多spatch的情况的子集)反而在编译时通过简单的重载和/或专门化来解决。通用性和快速性并不像其他语言让人思考的那样相互排斥。不需要额外的运行时开销就可以获得抽象。

    对于Barry Kelly,我认为C++不会因为面向值的编程而“受苦”,我相信你的意思是它强调正则类型,但它确实是最适合的,也是大多数其他主流语言都会出错的地方。复制构造函数、赋值运算符、移动操作和确定性销毁不是“聪明的技巧”,它们是在正则类型上表示操作的自然通用方法。垃圾收集和不确定性的处理是编程界使用的“聪明的技巧”,因为尽管对于编译器实现者来说更复杂,但对于更多的程序员来说更容易处理,并提供许多应用程序可以接受的结果。更重要的是,正则类型并没有使共享和编写泛型代码变得更困难,而是使其更容易,这实际上就是关键所在。这就是为什么您能够拥有与内置类型和用户定义类型一起工作的通用容器和库,并且不牺牲速度。这已经是Go在地图方面不必要的缺陷。

    [在下一篇文章中继续]

  • 里沃鲁斯 (2009年12月3日下午1:54)[续上一篇]

    现在我确实是一个C++程序员和爱好者,但我会告诉你一个小秘密。我们使用模板元编程,利用SFINAE,并做各种外人认为过于复杂的惯用C++因为我们喜欢这个挑战,但因为尽管它很复杂,但它比任何其他语言都更全面、更通用、更安全、更高效地表示概念、类型和通用算法。秘诀是你可以得到最终的结果没有一个类似模板的复杂功能,没有C++的许多毛茸茸的部分。通用程序员用C++编程,因为我们没有其他选项可以让您在静态类型的世界中完整高效地表达泛型库。

    像Java和C#这样的语言错过了这条船,我担心Go也会这样做。泛型,甚至是当前形式的Go接口,都不允许您使用Stepanov和Boost程序员一直使用的数学方法来创建泛型代码。如果你不能像Boost的Graph library那样编写一个通用且高效的库,那么如果你的目标是创建一种支持通用编程的语言,那么你只是缺少了一些东西。

    在我看来,如果Go要与C++的泛型程序员相关,它需要比C++模板更简洁地支持泛型编程范式,而不牺牲功能,虽然这很困难,但远不是不可能完成的任务。这一点尤为重要,因为C++0x已经放弃了对概念的直接支持,这意味着如果Go在自己的游戏中击败C++,它将真正进入自己的联盟。正如我在前面提到的,要满足大多数泛型程序员的需求,您真正需要的是一种表示概念、类型函数的方法,一种基于概念需求专门化/重载算法的方法,最好是类似于C++0x concept_maps的方法。这些都是构建一种支持STL等库的静态类型语言所需的全部,但其他主流语言却没有做到这一点。

    同样,Stepanov的“编程元素”是一个很好的地方,可以从中获得关于泛型编程的更数学的观点,我鼓励您阅读它。Go的界面是正确方向上的一个技巧,如果它们可以扩展到包含概念,您将开始看到更多的程序员从C++迁移到Go。

  • 信用证 (2009年12月3日下午2:43)基本类型的C#泛型(如int)

    是的,你可以申报。
    但T=T+1;在C#中不起作用。它在C++中工作。

    (有一些变通方法,但没有真正的解决方案。)

  • 噪音EHC (2009年12月3日下午2:46)有一个4。您没有考虑的选项:

    相反,实现一些元编程,并让程序员决定是否使用它来创建2。或a 3。解决方案。。。在这种情况下,动态加载模块(如核心库)的接口类似于1。

  • 列维 (2009年12月3日下午3:25)@正在进行{harper95编译,
    author=“罗伯特·哈珀和格雷格·莫里塞特”,
    title=“使用内涵类型分析编译多态性”,
    booktitle=“{POPL}'95:22第{ACM}次会议记录{SIGPLAN(SIGPLAN)}-{SIGACT}编程语言原理研讨会”,
    address=“加利福尼亚州旧金山”,
    pages=“130--141”,
    年=“1995”,
    url=“citeseer.ist.psu.edu/harper95compiling.html”}


    @文章{jones98transformationbased,
    author=“西蒙·路易斯·佩顿·琼斯和安德烈·桑托斯”,
    title=“{Haskell}的基于转换的优化程序”,
    journal=“计算机编程科学”,
    volume=“32”,
    数字=“1--3”,
    页码=“3--47”,
    年份=“1998”,
    url=“citeseer.ist.psu.edu/peytonjones98transformationbased.html”}

  • 安得烈 (2009年12月3日下午5:51)你看过D是如何做模板的吗?它使用C++风格的模板具有快速的编译时间。我不太清楚它对代码膨胀的影响,但除了极为人为的情况外,是否有任何C++代码膨胀导致速度减慢的实际例子?

  • 乔·谢弗 (2009年12月3日晚上8:00)也许可以看一下Ada泛型。通用实例是共享的,并且没有类型装箱。

  • 马尔基亚 (2009年12月3日9:18 PM)这样如何,如果在类型前面有@,那么延迟函数的编译,直到它首次被使用,然后为编译器生成相同的主体,但使用?类型已更改为实际类型。

    func-sum(a@type,b@type)@type
    {
    返回a+b;
    }

    功能测试()
    {
    a、 b整数8;
    c、 d整数16;
    总额(a、b);
    总额(c,d);
    总和(a,d);//错误,但请看下面求和的另一种方法
    总和(a,int16(d))//好 啊
    }

    func sum(a@typeA,b@typeB)@typeC
    {
    ca:=@typeC(a);//如果可能的话
    cb:=@typeC(b);//如果可能的话
    返回ca+cb;
    }

    不像C++中那样完整,但仍然

  • 德特埃尔扎 (2009年12月3日10:07 PM)激发了Java、Javascript和其他人灵感的Self语言将根据需要编译方法和结构的专门版本。超通用或动态型安全。这里有一篇很好的论文:http://research.sun.com/self/papers/elementation.html

  • 罗格·佩佩 (2009年12月4日4:27 AM)Vita Nuova的Limbo做泛型,但只针对指针类型。这可能会令人沮丧,但它们仍然非常有用,不会导致任何代码膨胀。

    这可能是一个起点——如果你能很好地做到这一点(而且要做到完全正确并不容易),那么“只是”生成代码来处理值类型实例化的问题。我不认为有任何方法可以解决大代码与慢速度之间的权衡,但您可以为N种优化情况(例如指针、32位字)和一种通用情况(任意大小的指针-int布局)生成代码,因此至少它都可以工作,并且如果需要,还可以生成更优化的代码。

  • 朱利安·莫里森 (2009年12月4日上午9:45)如果你看到了我之前的评论,就别理它,这是个坏主意。

    我认为我支持“不做”阵营。您可以使用接口{}参数显式装箱,并使用类型开关安全地取消装箱。如果您需要特殊实现的额外速度,请编写一个。

  • 恶魔机器 (2009年12月4日上午11:05)与其拥有一个可以在代码中任何地方使用的泛型系统,不如实例化一个泛型呢?

    类型string_hash是新的Generic_hash(string);

    这就是Ada的做法,这是Ada中为数不多的有意义的事情之一。

  • 乔纳森·阿姆斯特丹 (2009年12月5日10:13 PM)首先,选项3对于Go追求效率的语言来说没有意义。我甚至不认为这对Java有意义——这是因为他们需要保持语言和VM体系结构的落后——与大量现有代码兼容。这说明了在语言中添加泛型等待时间过长的危险。

    选项1,去掉非专利药,将导致以下一种或多种结果:市场份额大幅下降;使用不安全指针来模拟您在C中经常看到的void*样式的泛型;以及各种不太有用且不兼容的宏观解决方案。

    因此,应该把精力集中在选项2上,即纠正C++的缺陷。

    首先,速度:C++模板实例化是否很慢,因为标头不断被重新分析,模板使用相同的参数被多次实例化?Go没有这些问题:它不需要重新分析头,而且它可以从一开始就采用基于存储库的解决方案,其中实例化的模板会被记住,并且只生成一次。或者C++速度慢是因为模板被大量使用,例如用于智能指针、字符串等?Go已经有了一些内置的特性,这些特性消除了对一些常见C++泛型的需要(例如GC使智能指针变得毫无意义),并且通过使Go的泛型比C++的泛型更弱,例如消除了进行元编程的能力,您将减少实际编写的模板的数量。

    第二,空间:可以减少实例化的数量吗?似乎至少对于某些模板,您只能为所有指针类型生成一个版本,并且可能会为每个值类型大小生成一个附加版本。C#做了这样的事情,这篇关于Ada的文章可能会展示Intermetrics编译器如何处理这个问题:Gary Bray的“Ada泛型的实现含义”。(http://doi.acm.org/10.1145/989971.989974)不幸的是,ACM想让你付钱让他们阅读它。(顺便说一句,Joe Schafer:“通用实例是共享的,没有类型装箱”——我认为这不是真的,我怀疑32位整数和64位浮点会有两个不同的实例。)

  • Rivorus公司 (2009年12月6日下午2:49)我同意乔纳森的观点,但有几个问题除外:

    “Go已经具有内置功能,不需要一些常见的C++泛型(例如GC使智能指针毫无意义)”

    上述情况并不完全正确。如果程序员希望进行确定性处理,那么在需要共享所有权时,他们仍然必须求助于智能指针。

    “……通过使Go的泛型比C++的泛型更弱,例如消除了进行元编程的能力,您将减少实际编写的模板的数量。”

    问题是,编译时元编程是完全有能力的概念和模板的一种涌现属性。仅通过拥有完整的概念或模板系统暗示能够以某种形式进行元编程。唯一的解决方法是以影响最初预期用途的方式阻碍系统。

    此外,即使它是可行的,我也不同意删除一个有用功能的理由,因为它在使用时会影响编译时间。如果程序员需要快速编译,那么他就不会使用该功能。即使在C++中,也有一些程序员由于编译时间的原因不进行元编程,但(谢天谢地)他们的选择不会影响需要元编程的人。

  • 乔纳森·阿姆斯特丹 (2009年12月6日下午7:23)Rivorus-我理解你所说的包含元编程的完整概念/模板系统的意思。(还没有读过Stepanov,但我很想读。)但这并不意味着任何不合格的模板系统都是“残废的”。C#、Haskell、Eiffel和Ada的泛型与参数多态性理论有关,其中的思想是简单地将类型参数化而不是类型参数化。从某种意义上说,任何这样的系统都比完全元编程弱,就像正则表达式比图灵机弱一样。但这并不意味着它们有缺陷。他们只是在解决一个不同的问题。

  • 里沃鲁斯 (2009年12月6日11:41 PM)的确,我认为“残疾人”这个词比我想的要严厉得多。我的意思是,它排除了一种正在变得越来越普遍的特定编程方法,这是有充分理由的,尤其是在现代C++的泛型库中。如果您具有类型函数的功能,以及基于编译时类型和概念信息专门化算法和数据结构的方法,那么创建功能极其强大的泛型库的潜力很大。

    Boost Graph Library就是一个很好的例子,STL也是一个类似但规模较小的例子。如果语言要朝着支持更多通用代码的方向发展,我认为我们需要集中精力将这些思想转化为比当前C++模板更简单的东西,但不删除总体功能。

    由drhodes链接的论文是一个很好的参考。如果go能够比列出的所有语言更好地支持Boost Graph library这样的库的编程,那么它将是泛型编程的一个进步。在我看来,任何其他事情都是倒退,或者至少是不必要的停滞。

  • 桑博 (2009年12月7日下午3:44)>“IMHO,可组合的类型类非常适合Go的正交哲学。”

    +1

  • 新资本市场 (2009年12月11日下午2:44) “Go已经具有内置功能,不需要一些常见的C++泛型(例如GC使智能指针毫无意义)”

    用另一种语言复制库结构的内置特性表明了其弱点,而不是优点。它们意味着,如果没有特殊的大小写,这种语言就不足以满足这些需求。因此,我们可能会指出,C++的析构函数使GC毫无意义,并(进一步)说明GC的弱点。GC只能管理内存,但不能管理也使用析构函数进行常规管理的大量其他资源。(我们可以补充一点,作为一种架构辅助,由于缺乏析构函数,异常被削弱到几乎无用的地步。)

    将Haskell归为C#、Eiffel和Ada,这暴露了根本的混淆。C++和Haskell可以表达其他语言无法表达的内容。正则表达式确实是有缺陷的,除非您决定忽略您的语言无法解决的任何问题。“残障人士”是非常准确的。

    早就应该有一种像C++一样强大的更简单的语言了。遗憾的是,围棋不是这样的。围棋会成长,但它会受到早期不幸的设计选择的致命阻碍。C++将在2015年获得概念和模块。这些不会使它成为一种更简单的语言,但它将更容易编程,编译时间也会减少。这将带着我们,直到一个看似合理的继任者出现。

  • 朱利安·莫里森 (2009年12月11日下午3:06)ncm,您忘记了GC还管理所有权,并允许所有代码共享相同的所有权/销毁策略,这对于链接各种库的Java风格的粘合代码至关重要。

    “finally”块或Go的“defer”负责确定性资源回收。

  • 俄罗斯考克斯 (2009年12月11日下午3:12) C++将在2015年获得概念和模块。

    你是不是建议我们在那之前袖手旁观?

  • 新资本市场 (2009年12月11日下午3:42)“最后”障碍是对例外情况致命削弱的不充分回应。它们消除了错误处理的集中化,而这正是异常的体系结构目的。

    rsc:如果你愿意的话,你可以坐下来,或者开始开发一种更简单的语言,它可以像人们现在用C++做的那样强大。我们还没有,围棋也不会进化成它。很少有人能够在智力上或制度上开发出强大的新语言。

  • 俄罗斯考克斯 (2009年12月11日下午3:44) 围棋不会演变成它。

    如果你能解释原因,这将是一个更有用的评论。

  • 里沃鲁斯 (2009年12月12日上午1:11) “你是不是建议我们在那之前袖手旁观?”

    一点也不,我们希望Go能够正确地完成它,并在那之前完成它,因为你们不必像我们那样处理一个冗长的标准化过程(无论是概念还是模块都没有为C++0x做出贡献)。Go仍处于初级阶段,如果你现在能得到尽可能多的纠正,你就可以避免自己变得无关紧要或在未来需要胶带。C++确实很混乱,但我们之所以使用它,是因为它可以更全面地表达非常高级、通用和高效的库。

    如果你想让Go真正成为一门优秀的语言并吸引更多的C++用户,你就必须理解为什么C++如此强大。它不仅能让你像C一样低级,还能为泛型编程提供一些最佳支持,尤其是与其他主流语言和Go相比。C++社区与该语言有着亲密的关系。我们喜欢它能做什么,我们讨厌它一团糟。制作一种像C++一样强大的语言需要理解泛型编程,最好能够以更简洁的方式完成它所能完成的任务。

    随着对范例的更好理解,以及STL、Adobe/Boost的Generic Image Library和Boost Graph Library等库的出现,我们已经了解了应该存在哪些基本功能来实现适当的泛型编程。尽管像C#这样的语言中加入了大量功能,但它们仍然无法实现。特别是,你应该有能力用关联类型表达概念、概念细化、基于概念参数模型的算法重载/专门化,以及生成类型函数的能力。C++之所以努力,是因为它足够强大,可以在某种程度上通过模板巧妙地表示其中的许多特性。您所要做的就是直接支持此功能,并且您将在通用编程方面领先一步。

  • 俄罗斯考克斯 (2009年12月12日上午6:47)我打算再试一次。

    Go,很遗憾,不是吗?Go会成长,但由于早期不幸的设计选择,它受到了致命的阻碍。

    围棋不会演变成它。

    如果你解释一下你认为那些不幸的早期设计选择是什么,以及为什么围棋不能进化成“它”,这些评论会更有帮助。

  • 里沃鲁斯 (2009年12月12日下午12:55)[多部分帖子]

    “如果你解释一下你认为那些不幸的早期设计选择是什么,以及为什么围棋不能进化成“它”,这些评论会更有帮助。”

    我不是ncm,我比他乐观一点。我确实认为是Go可以发展成为通用编程的坚实语言,尽管某些已经存在的东西需要改变。这是一个你作为语言设计者是否想朝着这个方向发展的问题。ncm可能只是说Go已经走上了“错误”的方向,对该语言的某些基本思想的理论基础有缺陷,虽然我同意,但从常见问题的外观来看,你听起来很容易改变。无论如何,我不想对ncm说什么,我相信他很快就会回应。

    这是我个人对事情的看法,没有特别的顺序。关于语言FAQ,我个人突然想到的一个“问题”是这个问题为什么当数组是值时,映射、切片和通道引用。提供的理由非常薄弱——它讨论了映射实现的早期是“语法上的”指针,并且您无法创建非动态实例。此外,它还说“……我们一直在努力研究阵列的工作方式。”这两个都不是问题的答案。

    我将尝试从C++的角度对此进行解释。在C++中,映射和所有其他容器都是值。事实上,除了一些令人尴尬的例外,C++中的所有对象类型都具有正则类型的适当语义(这些例外尤其是遗留的C数组,这是TR1/boost数组模板存在的原因之一,而std::auto_ptr作为-i存在的原因是当前标准中缺少移动语义,并且需要高效、安全地从函数返回动态分配的数据)。正则类型的一致语义对泛型编程非常重要,这就是为什么C++中的泛型数据结构如此强大的原因。您需要一种通用的方法,通过一致的高级菜单高效地使用所有类型的基本功能(例如,一个用于可复制性的单一接口,以及这对于相等性意味着什么)。

    同样,常见问题解答指出:

    “映射查找需要一个相等运算符,结构和数组不实现该运算符。它们不实现相等因为在此类类型上没有很好地定义相等; 有很多考虑因素涉及浅比较与深比较、指针与值比较、如何处理递归结构等等。我们可以重新考虑这个问题,实现结构和数组的相等不会使任何现有程序无效,但不清楚结构和数组相等的含义是什么,现在把它去掉比较简单。"

    正如我在上一段中提到的,您需要表示正则类型的概念。您已经对数组的可复制性有了一个概念,这应该会提示如何实现相等。根据定义,复制某物意味着创建另一个具有相同值的实例。公民平等是一个单独的问题,不应混淆。复制的行为意味着每个对象的值现在都相等,这应该由定义良好的相等运算符来反映。如果复制后两个对象的比较结果不相等,则复制操作不是真正的复制操作,或者相等操作没有正确定义。

  • 里沃鲁斯 (2009年12月12日下午12:58)[多部分文章续]

    另一个我很不同意的决定是缺少C++程序员所知道的析构函数。这是非常简短的提及在这里基本原理几乎不存在。我只能相信这样的功能被忽略了,仅仅是因为Go的设计者不了解析构函数到底有多重要。赫伯·萨特已经很好地涵盖了这个主题,并解释了为什么析构函数应该是比Go的“defer”等功能更可取的默认值。这并不是说defer不好,只是它不能替代析构函数。无论何时使用类型,都必须显式地使用“defer”,而不是在类型的定义中指定一次,然后再隐式地使用。

    围棋语言规范您可以看到一个很好的示例,其中析构函数比defer语句更适合。当你创建一个锁时,永远不要解锁,或者在不确定的时间解锁,这是一个非常糟糕的主意。析构函数使其自动且隐式。没有可以忘记编写的defer语句,因为已经暗示了解锁。事实上,这正是Boost中锁的实现方式,以及它们在下一个C++标准中的实现方式。管理资源的任何类型都存在类似的析构函数需求,即使不是大多数非平凡类型,也有很多。RAII很重要,构造函数/析构函数是在C++中实现它们的关键方式。目前在Go中没有简单的隐式方法来实现这一点。

    这一点已经成为析构函数的有力论据,但对于泛型编程来说,缺少析构函数意味着什么呢?实际上有很多。每当您将一个类型与通用数据结构或算法一起使用,并且该代码负责创建类型的实例时,您应该希望在该类型的实例离开作用域时,可以确定地清理资源。如果您不知道到底需要清理什么,那么就不能在泛型代码中编写defer语句。这是泛型代码不知道(也不应该)的实现细节。使用析构函数等功能,正确的行为会隐式发生。

  • 里沃鲁斯 (2009年12月12日下午1:04)[多部分员额续]

    最后,还有过载的问题。再一次常见问题解答有一个非常薄弱的理由,谈论这样一个事实,即它使语言的实现更简单,而且可能会“令人困惑”。某些事情起初可能很难理解,但这并不意味着它们不是重要的才能理解。对于许多程序员来说,重载已经是第二天性了。不仅如此,它对泛型编程也极其重要,忽略它意味着无法创建高效的泛型库。

    在静态类型语言中,重载调度基于数据类型,在理想情况下,基于它们建模的概念。在一个用静态类型语言设计的通用程序中,你有围绕越来越小的算法构建的高级通用算法,每个算法都在概念模型上运行。如果有一个更精细的概念或特定类型可以更有效地实现给定的算法,则该算法将为该类型或概念重载。在C++中,由于所有重载都完全基于类型/概念数据,因此在编译时决定分派哪些函数,您的开销为零。

    这种类型的重载对于泛型编程是如此必要,而简单地以不同的方式命名函数并不能解决这一问题,这是因为算法的层次性。A调用B,B调用C,C调用D和E等,每一个都有一个通用实现,可以处理任何对特定概念建模的东西,例如,访问任何需要线性时间的元素的任何容器或范围。现在想象一下,当算法“C”被传递给一个允许在恒定时间而不是线性时间访问任意元素的范围时,它可以以更高效的方式编写。如果您可以基于此类型信息重载C,那么您实际上可以使算法A和B在您以恒定时间访问任何元素的方式传递范围时自动更高效。

    如果没有基于类型系统重载的能力,就无法获得可组合且高效的泛型代码。参考上面的示例,为特殊情况优化的情况创建一个不同名称的“C”,而不是重载,这意味着必须创建一个名称不同的“a”和“B”,如果您希望它们利用您的优化代码的话。这意味着,如果需要提高效率而不是免费获得,则必须编写更多的算法。现在,使用您的算法的任何人都必须知道,如果希望获得更优化的实现,则必须使用新名称来命名您的特殊情况算法。如果他们的代码已经编写好了,这可能意味着如果他们需要更快,就必须重写它,因为实现细节在链的某个任意位置发生了更改。这对每个人来说都是坏消息。由于类型系统、重载和SFINAE,所有这些在C++中都是可能的。虽然Go中的概念式功能很好,但没有重载和相关类型,这只是半途而废。

    这些只是我个人对Go的一些问题。其中很多可以改变,FAQ让人觉得你对这种改变的可能性非常开放。事实上,你已经朝着你所称的“接口”迈出了大胆的一步(它基本上只是泛型编程中“概念”的精简版本),并且远离了传统的宗教OOP,这说明了很多。我相信你们正在努力提供一种固体语言——一种在效率和编写通用代码的能力方面与C++相比的语言。如果这真的是你的目标,我认为你一定需要注意C++做得对的事情,以及如何修改或改进以提供更好的东西。现在,你似乎没有学到C++及其泛型库所教的课程。

  • 乔纳森·阿姆斯特丹 (2009年12月12日下午5:47)Rivorus,我钦佩你的热情,但你的乐观主义是错误的。围棋设计者不太可能不知道你的论点。我想他们只是觉得泛型编程的好处与它给语言增加的重量不成正比。或者,使用一个他们经常使用的单词,也不会使语言变得更加“有趣”。您在Go中看到这些特性的可能性与在Scheme中看到静态类型的可能性差不多。每个人都有问题;归根结底,这是一个味道问题。我认为最好的希望是一个简单的泛型类型工具。

    你会注意到Go设计者确实在他们认为值得的地方增加了复杂性。例如,他们的反射功能比C++强大得多,有人可能会说,对于许多典型的现代编程任务,如XML序列化(请参阅本博客稍后的Russ文章),这种能力比通用编程的能力更有帮助。(我毫不怀疑,您可以找到一种使用模板元编程将XML解析为C++结构的方法,但我认为这不太好,当然,I/O时间无论如何都会占主导地位,因此它的效率也不会更高。)

    技术要点:您可以在Go中实现析构函数式的资源管理,如下所示:
    ------
    接口Disposable{Dispose()}

    func使用(r一次性,f func()){
    defer r.Dispose();
    f();
    }

    //示例
    连接:=数据库。新连接(connString);
    使用(conn,func(){
    …连接。。。
    });
    --------
    由于有了接口,这也可以一般地完成。它还演示了Go的另一个强大特性,匿名闭包,这是C++刚刚获得的。

  • Rivorus公司 (2009年12月12日下午6:07) “我毫不怀疑,您可以找到一种使用模板元编程将XML解析为C++结构的方法,但我认为这不太好,当然,I/O时间无论如何都会占主导地位,因此它的效率也不会更高。

    非常正确,但是使用Boost。在C++中的Spirit库,你可以很好地做到这一点,它的直观性也令人惊讶。当然,除非你真的像你所指出的那样对元编程感兴趣,否则你不想查看所有可能的库代码。

    “技术要点:您可以在Go中实现析构函数式的资源管理,如下所示:”

    这正是我所说的。你发布的正是析构函数用更少的代码隐式完成的类型。同样,如果您忘记了,只需延迟,默认情况下就会出现“不正确”的解决方案。你发布的代码类似于我链接到的Herb Sutter在帖子中提到的C#或Java代码。他在谈论Java和C#时所说的一切都适用于Go中关于延迟的部分,减去关于异常的部分,因为Go[目前]没有异常。

  • 乔纳森·阿姆斯特丹 (2009年12月13日上午8:57)首先,让我们抓住争论的线索。你的说法是延迟干扰了通用编程。我证明了这是错误的,假设每个人都通过了某个公约。(你现在可以说,围棋并没有为促成这样一个惯例做任何事情,我同意你的观点。)

    现在,让我们讨论析构函数与用于资源管理的对比:

    关于Sutter的帖子的一些小问题:他错误地编写了Transfer的Java版本。在正确的版本中,评论员关于Dispose抛出异常的观点不适用。(您可以嵌套try块。)我不知道他为什么认为您必须等待GC来释放队列的资源。Dispose()将立即执行此操作。

    但我同意萨特的主要观点:在适用的情况下,析构函数更好。我的大部分日常编程都是用C++编写的,我喜欢这种语言的这一方面。但我们应该考虑它们的适用频率。对于文件I/O来说,这是肯定的,因为最常见的模式是完全读取或写入。对于锁来说根本不是,因为锁的寿命总是比锁定它们的范围长;因此,在C++中,您需要一个RAII包装器类,代码与“using”的代码大致相同。有时用于网络连接;但通常情况下,您希望在断开连接之前保持连接一段时间,并且可能希望异步释放它,比如在它空闲一段时间后。您最终得到了某种连接池,然后我认为C++和C#中的代码看起来没有什么不同。

    值得指出的是,垃圾收集语言的缺点是不需要删除,并且在释放资源后,典型的垃圾收集器可能会在任意距离的时间点运行。我认为Limbo有一个很好的解决方案:它对非循环结构使用了引用计数,并保证它们在未被引用时立即被释放。实际上,资源拥有对象几乎从不循环,所以这个想法应该很有效(尽管我没有实际经验)。

    至于围棋,我不认为它采用了析构函数,因为这会导致构造函数、赋值运算符等的出现,围棋设计者非常清楚,他们不想走这条路。Go可能会采用GC终结器,但除此之外,我认为我们不应该期望该语言提供任何帮助。

  • 朱利安·莫里森 (2009年12月13日上午11:37)我真的看不出立即释放内存有什么好处。“免费”不是免费的。当压缩GC可以清除并批量释放内存时,您正在将内存分为小块进行洗牌。

    如果您需要通过接口访问需要清理的资源,那么您可能应该将“Release()”方法作为接口的一部分,您可以推迟使用。

  • 新资本市场 (2009年12月13日上午11:39)谢谢你,里沃鲁斯和乔纳森,花时间把我想写的东西表达得更透彻、更圆滑,至少也同样深思熟虑。

    乔纳森,我认为你误解了“延迟”的局限性。在R.的ABCD示例中,如果C中的某些内容调用了延迟代码,则编码A的人无法知道这一点,因此无法写入。析构函数是唯一可以放置它的地方。

    语言内置程序执行跨库约定的观点很好,尽管我不同意其含义。

    我们已经在C++标准库中看到了一些示例,这些示例对用户代码施加的约定不是由语言决定的,而是由与该库的兼容性决定的,例如不从析构函数中抛出。语言的功能越强大,可能已经内置的东西越多地放在标准库中,就越需要有人能够规定这样的约定。这不是内置的论点,而是将跨库需求形式化的论点。

    C++社区一直站在学习如何表达此类需求的最前沿。在一种比C++更强大的语言中,我们认为内置的更多内容(参数列表、循环控制结构、vtables?)可以提取到标准库中,并相应地更多地依赖于这种兼容性要求。

  • 里沃鲁斯 (2009年12月13日下午12:40)【再次分为两部分】

    “你声称延迟会干扰通用编程。我证明了这是错误的,假设每个人都采用了某种约定。”

    你什么也没做。你完全展示了我所说的,你必须跳出圈套,每次用非平凡的处理方式创建对象时都要显式,而实际上你应该得到对的行为隐式地默认情况下,不会出错。该语言是图灵完备的,没有人会说,对于给定的应用程序,无论有无destrutor,都无法达到相同的最终目标,只是说,没有destrutors,就更加困难,而且更容易出错。

    “我不知道他为什么认为您必须等待GC释放队列的资源。Dispose()会立即这样做。”

    他并没有说Dispose不会这样做,他说如果你作为程序员没有显式地Dispose,那么在对象离开作用域(finalization)之后,你会在非决定性的时间释放资源。这在使用析构函数时根本不会发生,因为正确的行为会自动发生。

    “他错误地编写了Transfer的Java版本。在正确的版本中,评论者关于Dispose引发的异常的观点将不适用。(你会嵌套try块。)”

    他正在编写最简单的代码,其最终结果与他发布的相应C++/CLI代码相当,以说明他必须明确的观点。他本可以嵌套try块,但这只会使代码比现在更复杂。如果你真的想学究式地,你也会遇到C++和Java中处理异常的方式之间的根本区别——在C++中,抛出但从未在析构函数中捕获的异常不会传播,它们会导致终止,这意味着如果您真的想让代码尽可能与C++相似,那么不仅需要所有这些try/cates,而且如果dispose允许异常泄漏,那么您将终止而不是传播。所有这些都超出了他的帖子的重点,但只是进一步证明了支持使用析构函数的方法。同样,析构函数是自动调用的,而Java和C#的“defer”或dispose模式要求每次实例化具有非平凡处理的类型时都是显式的。

    “但我们应该考虑它们的适用频率。当然是对于文件I/O而言,因为最常见的模式是完全读取或写入。对于锁来说,一点也不适用,因为锁的寿命总是比锁定它们的作用域长;所以在C++中,您需要RAII的包装类,代码与您使用的代码大致相同”使用“。有时用于网络连接;但通常您希望在断开连接之前保持连接一段时间,并且您可能希望异步释放它,比如在它空闲一段时间后。您最终会得到某种类型的连接池,然后我认为C++和C#中的代码看起来没有什么不同。”

    所有这些情况都是析构函数适用的完美例子,事实上,我一直在这些情况下使用RAII,当然不会用它来换取“defer”。在您正在讨论的假设情况中,例如,在锁定对象超出范围后,锁被解锁(我不知道为什么您总是这样认为,实际上几乎从来没有这样过),您所展示的是这样的情况:您要么在一个范围中声明锁定对象,然后再锁定,或者,可能性较小,您希望共享锁对象的所有权。使用C++,您可以非常轻松地自动处理这两种情况,并且在所有对象结束其生命周期后,仍然可以立即获得确定性解锁,几乎没有出错的可能。此外,我甚至不确定你在这里试图指出什么,因为你没有提供一个更好的“推迟”方法的例子。

  • 里沃鲁斯 (2009年12月13日下午12:42)[续]

    请记住,我从来没有说过“defer”没有什么用处,只是析构函数更适合于几乎所有常见的情况,并且是自动的,而不是显式的和容易出错的。在析构函数上使用类似“defer”的特性的时候,您需要对特定范围执行一些独特的操作。这确实发生了,这就是为什么“延迟”非常有用的原因。这两个功能绝不是相互排斥的。

    由于您似乎坚持RAII不适用于线程或网络,请参阅促进。螺纹促进。亚洲证券投资组织(异步输入输出库),这两个库都非常棒,可以像您声称RAII通常不适用的域中的大多数其他C++程序一样正确地使用RAII。

    “至于Go,我不认为它会采用析构函数,因为这会导致构造函数、赋值运算符等的发展,Go设计者很清楚,他们不想走这条路。Go可能会采用GC终结器,但除此之外,我认为我们不应指望语言提供任何帮助。”

    好吧,我很抱歉,但我觉得他们在语言设计中犯了一个非常根本的错误,因为我不能容忍那些让正确编程变得更困难而不是更容易的决定,因为他们想让语言“简单”。尽可能简化,但不要再进一步。

  • 里沃鲁斯 (2009年12月13日下午12:44)致朱利安:

    “我真的看不到立即释放内存的好处。”“free”“并不是免费的。你是在把内存分成小块,当压缩GC可以清除并批量释放内存时。”

    我们并不是专门讨论释放内存,而是讨论释放重要资源、关闭文件、关闭连接、释放锁等。

    如果您需要通过接口访问需要清理的资源,也许您应该将“Release()”方法作为接口的一部分,您可以推迟使用

    同样,如果不使用,这是显式的,默认情况下是不正确的。析构函数是自动且正确的。

  • 里沃鲁斯 (2009年12月13日下午1:10)回到Russ,在这里是另一篇关于泛型编程的好论文。特别是,它更多地谈到了“概念”。

  • 朱利安·莫里森 (2009年12月13日下午1:13)你在抱怨它太露骨了。我认为这不是一个错误,我认为这是一个功能。

    如果我正在查看C++(以及某种程度上的Java、Ruby等)代码,我想问“它在哪里返回?它在哪里突然决定执行昂贵的操作?”答案是在任何地方。如果它现在不这样做,谁知道下一个库版本会带来什么?

    如果我在看Go代码,我想问同样的问题,答案是:它返回它说“return”的地方,或者在函数的末尾。它在你说“defer x.cleanup()”的函数末尾进行清理。就这样了。搜索可以结束。无需任意深入挖掘,也无需盲目依赖可以为您挖掘的IDE(就像我在Java中不得不在庞大的代码库中挖掘的那样)。

  • 里沃鲁斯 (2009年12月13日下午2:34) “你在抱怨它太露骨了。我不认为这是个错误,我认为这是一个功能。”

    你所说的是,这是一个“特征”,你可以错误地使用你的对象。你可以随意调用它,但这不会改变它是什么。如果你想显式地调用它,那么就可以显式地使用它,但如果你没有编写它,也会使它成为编译器错误。你现在收获甚少?

    “如果我正在查看C++(在某种程度上还有Java、Ruby等)代码,我想问“它能返回到哪里?”?它能在哪里突然决定进行昂贵的手术?“答案随处可见。如果它现在不这样做,谁知道下一个库版本会带来什么?”

    它可以在相同的地方进行“昂贵的操作”。运行函数时。唯一的区别是,当对象的生存期结束时,函数会自动正确运行。如果应该运行的基本功能没有在那里运行,那么这是一个错误。强制程序员显式地编写“defer”语句只意味着他们可能会忘记或错误地这样做。

    如果您是说程序员查找“defer”和相应的范围结束,那么程序员可以很容易地在类型的实例化方面执行相同的操作。如果在实例化除内置算术类型以外的类型时,我没有看到“defer”,而不是说“没有‘defer’语句,函数末尾什么都没有发生”,我更可能会说“没有’defer‘语句,处理很简单,或者程序员忘了写‘defer‘。”使用自动销毁,在任何情况下都会发生正确的行为。

    这并没有什么可怕的复杂——什么复杂是每次实例化类型时,都试图显式地正确模拟该行为。这就是产生错误的原因。

    下面是一个更好的例子,因为你说:

    “……谁知道下一个库版本会带来什么”

    销毁器的情况是自动的,相对于未来的发展来说更安全。假设类型“A”具有琐碎的处理,因此程序员决定在实例化它时不使用“defer”语句。在开发的后期——几周、几个月或几年——类型“A”会增长,实现细节会改变到现在应该关闭资源的位置。当实例化“a”时,突然之间应该会有延迟处理。

    那么前面写的代码会怎么样呢?由于没有使用“defer”,您现在必须返回并将其插入到任何位置,否则将无法正确清理任何内容。

    解决这个可怕场景的方法是让原始类型“A”具有一个空的“dispose”方法,实例化它的地方具有一个调用空dispose的“defer”语句,假设将来“A”可能会演变为具有非平凡的dispose。如果遵循此模式,则在实例化类型的作用域绑定实例的任何地方都会有一个显式的“defer”,即使其dispose当前不执行任何操作。

    那么,与自动化相比,这有什么好处呢?如果每次实例化类型时都应该延迟处理,那么隐式组合操作绝对不会损失任何东西。您现在已经使该类型不可能被滥用。

    这甚至不包括拥有共同所有权的地方,在这些地方,在没有更多所有者之后,处置是决定性的。您仍然需要进行额外的管理——管理是在C++中自动完成的,因为RAII与智能指针有关。只使用“defer”这样的特性来实现这一点是不可行的。

    和许多语言一样,很容易走上过于简单化的道路。每个人都希望自己的语言简单,但你只会因为去掉了使繁琐和容易出错的操作自动化的功能而失败。你可以拥有一种简单的语言,而不必放弃它。

  • 朱利安·莫里森 (2009年12月13日下午3:04)“x=NewX();defer x.Destroy()”是一个习惯用法-紧跟在实例化之后的defer。任何读过示例代码的人都会惊讶地发现没有延迟。这将是一场“巨大的沉默”。

    您可能有一些原因不想销毁x。它可能会在闭包中捕获。它可能包含在返回值中。它可能会通过一个渠道传播。看到没有延迟,人们就会知道寻找x的逃亡。

  • 里沃鲁斯 (2009年12月13日下午3:30)当您通过间接进行逻辑处理时,您不希望它被销毁。您或者有多个对象通过间接“拥有”同一对象,可能有一些对象对其引用较弱,或者您可能总是只有一个所有者,而该所有者在对象之间进行交易,等等。

    在所有这些情况下,RAII仍然完美无缺,仍然提供自动和安全的解决方案,在所有情况下,您仍在努力实现确定性处置的目标。更重要的是,由于生命周期的管理方式是您实现间接寻址所使用的类型的一个属性,因此您可以为代码读者提供比“没有延迟处理,其他人会处理它”更多的信息,您可以告诉他们“这正是管理此对象生命周期的方式”。这比你描述的“巨大的沉默”更能说明问题,程序员不可能简单地忘记处理。

    对于那些不喜欢C++的人来说,这听起来很可怕,但聪明的指针就是例证。

  • 新资本市场 (2009年12月13日下午8:33)顺便说一句,Russ,这削弱了对模板的旧“膨胀”诽谤的整个讨论。在过去这不是真的(这似乎是应该的,这足以成为诽谤),今天也不是真的。不仅“好的”链接器将重复的实例化折叠在一起。linker做到了,他们从一开始就这么做了。

    当然,Java和C#中有很多膨胀的程序,无论在哪里都是如此,但膨胀并不是来自模板实例化。我们总是会有膨胀,因为膨胀总是来自同一个地方——糟糕的程序员——而我们总是会遇到太多这样的人。

  • 乔纳森·阿姆斯特丹 (2009年12月14日12:30 PM)@ncm:Russ提到的是“库”,而不是可执行文件。我认为编译器会输出向量<int>的多个实例化,每个.o一个,这会导致库变大。然而,我不知道为什么这会引起任何人的关注,除非他们实际上是在嵌入式设备上进行“开发”,而不是仅仅在其中进行“部署”。(我刚花114美元买了一张1TB的磁盘。)

  • 乔纳森·阿姆斯特丹 (2009年12月14日下午12:49)@Rivorus公司:
    我同意在理论上析构函数比defer更好,但我认为在实践中,您不会看到许多抽象模式以一般方式获取和释放资源,并且不知道它是一种资源。首先,这种资源获取类的构造函数对于每个资源(文件、数据库连接等)都是特定的,并且从来都不是no-arg构造函数。所以我认为在现实世界中,很少有通用代码不确定是否使用defer。从理论上讲,你的方法显然是优越的,但在实践中增加复杂性值得吗?我相信这就是围棋人的想法。

    关于穿线,我认为我们彼此误解了。我从未声称RAII没有用处。也许一些代码会有所帮助:

    类计数器{
    私人:
    整数c;
    互斥体m;
    公众:
    计数器():c(0){}
    void inc(){锁(m);c++;}
    };

    这里有两个对象,互斥对象和锁包装器,名为锁。你两者都需要。我的观点是,写“lock(m)”和写“using(m){..}”或“m.lock();defer m.unlock(()”需要相同数量的大脑,所以RAII不会通过显式方式为您购买任何东西。

  • 塔伦·伊兰卡特 (2010年9月17日下午5:25)一年后,我偶然发现了这篇旧帖子,Rivorus的精彩论据和富有洞察力的评论激励我掸去C++模板书上的灰尘,同时寻找Stepanov的书……:)

  • 匿名(2011年1月4日10:23 PM)大多数通过代销商赚钱的人都会注册几个代销商计划。事实上,在你找到那些能让你尽可能多的东西之前,你必须用几个做实验。作为附属公司,考虑选择推广产品的一个重要方面是选择有价值的产品。如果你不买它或不使用它,那么你的客户很可能也不会买。记住,即使你是通过网络而不是亲自销售,无论你是否真的相信你正在推广的产品,都会在你的营销努力中表现出来。如果你想说服别人购买你真正相信的产品,那么就选择它们。

    当做,
    [url=George(IT人员)[/url]

  • 卡斯滕·米尔考 (2011年5月27日上午5:35)你忘了

    4.(python方式)使类型成为一级对象。这意味着,类型工厂是全面的普通函数,而不是词法模式。

    将解释的弱类型语言与编译的强类型语言进行比较当然是不公平的。尽管如此,Go在Reflect包中已经朝着这个方向发展了很多。

    简单地说,泛型实际上是对类型进行计算的一种特殊方法。在许多情况下,它们都是简单明了的,但Python表明一级类型也可以做到这一点,而boost库的快速发展表明,实际上对一般类型计算的需求很大(而且泛型在这方面也较差)。编译是一种计算,只有使用与运行时计算相同的语言进行编译时计算才有意义,不是吗?

    我不认为这很容易,一点也不容易。然而,我认为遵循这条道路将使围棋变得更加有用、富有成效和独创性。

  • 匿名(2011年6月6日下午7:30) “x=NewX();defer x.Destroy()”是一个习惯用法-紧跟在实例化之后的defer。任何读过示例代码的人都会惊讶地发现没有延迟。这将是一场“巨大的沉默”。

    哦,所以当分配对象的函数返回时,每个对象都会被销毁,这是一个“习惯用法”吗?写这些东西的人真的是以编程为生吗?颤抖。我当然希望我没有使用他们的任何代码。乔纳森·阿姆斯特丹(Jonathan Amsterdam)也是如此,他犯下了更严重的错误,就是处理了在调用程序中分配的对象——而且是手动的;他很快就忘记了推迟的关键是保证对象在不再使用时被销毁。当然,这些示例共同说明了这一点——在所有情况下正确使用dtor(RAII)的唯一方法。

    此外,Jonathan的代码示例突出了Go接口的一个致命弱点——它们不可组合。假设我想在将r传递给一个需要Foobler的函数后对其进行处理——如何将r声明为两者都是
    一次性和傻瓜?我不能。。。我永远也不会这样做,因为可组合性会与Go对无类型层次结构的简单(有意识)设计相矛盾,并且类型语法的设计是严格的,这使得添加此类功能非常困难。

    Rivorus,我钦佩你的热情,但你的乐观主义是错误的。

    同上。

    围棋设计者不太可能不知道你的论点。

    证据强烈表明情况恰恰相反。一位设计师的这条线索就是反证。

    你是不是建议我们在那之前袖手旁观?

    漂亮的稻草人,扭转了现实。ncm没有提出这样的建议,而是从对Go的观察中得出了结论——“不幸的早期设计选择”。这不仅仅是语言中的选择,而是选择关于语言——基本设计理念。现在是2011年年中,没有任何通用设施的迹象。从这种设计理念和Go异常的弱得令人难以置信的版本来看,人们可以预测,如果到2015年它有泛型,那么它们也将弱得令人无法相信。正确使用泛型需要大量的仔细工作相信并意识到自己在编程语言设计方面的力量和积累的丰富经验。例如,看看D、Scala和Jade——Emerald的派生产品,正如您所知,它的界面与Go的非常相似。

  • 教堂 (2011年8月22日上午9:36)你好,Russ,
    自从这篇文章发表以来,已经过去了将近两年,go仍然不支持泛型编程。
    非专利药在当今围棋中的地位如何?你还在想这个吗?
    在对泛型进行了这么多讨论之后,想知道你的结论是什么会很有趣。
    谢谢。

  • 匿名(2011年10月25日上午5:03)http://www.bluebytesoftware.com/blog/2011/10/23/OnGenericsAndSomeOfTheAssociatedOverheads.aspx

  • 匿名(2011年12月8日上午11:09)我认为在Go中使用泛型没有任何好处。这将使语言更加复杂,但没有明显的好处。