我在SO中搜索了一段时间,找不到一个明确而笼统的答案,只有一些矛盾和特殊的意见。[1]
所以我想知道鸭子类型和泛型编程之间的关系是什么?(DT < GP,DT == GP,DT > GP)。通过泛型编程,我特别指的是 C++ 模板或 Java 泛型,但如果可能的话,与概念相关的一般答案将受到欢迎。
我知道通用编程将在编译时处理,而鸭子类型将在运行时处理,但除此之外我不知道如何定位它们。
最后,我不想开始辩论,所以我更喜欢诸如理由之类的答案,反对理由。
我在SO中搜索了一段时间,找不到一个明确而笼统的答案,只有一些矛盾和特殊的意见。[1]
所以我想知道鸭子类型和泛型编程之间的关系是什么?(DT < GP,DT == GP,DT > GP)。通过泛型编程,我特别指的是 C++ 模板或 Java 泛型,但如果可能的话,与概念相关的一般答案将受到欢迎。
我知道通用编程将在编译时处理,而鸭子类型将在运行时处理,但除此之外我不知道如何定位它们。
最后,我不想开始辩论,所以我更喜欢诸如理由之类的答案,反对理由。
我遇到了“鸭子打字”的两种不同定义。毫无疑问,有人会告诉你其中一个是“正确的”,另一个是“不正确的”。我只是试图记录它们都被使用而不是告诉你它们都是“正确的”,但我个人认为更广泛的含义没有错。
1) 仅运行时类型。类型是对象的属性,而不是变量,因此当您调用对象的方法或以其他方式使用对象因类型而具有的属性时,必须在运行时发现该方法的存在与否[* ]。因此,如果它“看起来像鸭子,叫起来像鸭子”(即,如果它有quack()
功能),那么它“就是”一只鸭子(无论如何,你可以把它当作一只鸭子来对待)。根据这个定义,C++ 模板当然是第一个障碍,它们使用静态类型。
2) 一个更普遍的名称,用于表示如果它看起来像一只鸭子并且嘎嘎叫起来像一只鸭子那么它就是一只鸭子的原则,表示任何接口由接口消费者执行的操作隐式定义的设置,而不是而不是由生产者明确公布的接口(无论实现接口的)。根据这个定义,C++ 模板确实使用了一种鸭子类型,但是“看起来像鸭子”的东西是否“看起来像鸭子”是在编译时确定的,而不是在运行时,基于它的静态类型而不是动态类型。“编译时检查,这个变量的静态类型能不能嘎嘎?”,而不是“运行时检查,这个对象能不能嘎嘎?”。
在我看来,争论实际上是关于 Python 是否“拥有”这个术语,因此只有类似 Python 的类型系统才能被称为鸭子类型,或者其他人是否可以自由地将这个术语用于在不同的上下文中表示类似的概念. 无论您认为它应该是什么意思,使用“开玩笑”的术语并要求每个人都从中理解相同的正式定义似乎是不负责任的。SO 不是一个合适的论坛来告诉您“应该”一词是什么意思,除非您要询问权威来源,例如字典或任何正式定义它的学术论文。我想它可以告诉你它实际上是什么意思。
“通用编程”可以使用或不使用鸭子类型,具体取决于具体细节。C++ 通用容器确实使用“类型 2”鸭子类型,因为对于 C++ 中的通用类型,不能保证您可以复制、比较它、获取哈希值等。Java 通用容器没有,Object
已经有足够的方法使其可散列等。
相反,我怀疑您所做的任何使用鸭子类型的事情都可以合理地称为“通用编程”。所以我想,按照你要求的术语,鸭子类型(任一定义)中的 GP > DT 是可以称为“通用”的大量内容的严格子集。
[*] 好吧,在某些情况下,您的动态语言可能会进行一些静态分析,以一种或另一种方式证明这种情况,但是对于静态分析无法最终确定的情况,该语言要求能够将此检查推迟到运行时。
这确实是词汇的问题。在最一般的意义上,泛型编程与编译时与运行时的问题无关:它是对一般问题的解决方案。Python 是运行时泛型编程的一个很好的例子,但也可以在 C++ 中实现运行时泛型编程(执行时间成本很高)。
鸭子类型是一个正交概念,通常用于暗示运行时类型。同样,最常被引用的现代示例是 Python,但从 Lisp 开始的许多语言过去都使用过它。作为一般规则,C++ 和 Java 都明确选择不支持鸭子类型。这是一个权衡:安全性与灵活性(或编译时错误与运行时错误)。
Java 不支持该语言的鸭子类型。它确实支持可以实现相同目的的反射。据我所知,它与 Java 的泛型没有任何关系,实际上让它们一起工作真的很痛苦。
对我来说,“鸭子类型”意味着没有明确的一致性关系。如果一个东西像鸭子一样走路和像鸭子一样说话,它可以被视为鸭子,不需要明确声明它是鸭子。在 C++ 术语中,它不需要从 Duck 基类继承:继承是一种明确声明一个类符合另一个类的接口的方式。
这个概念与类型检查发生在运行时还是编译时是正交的。像 Smalltalk 这样的语言提供了在运行时发生的鸭子类型(并且继承用于重用实现,而不是声明接口的一致性)。C++ 模板是在编译时发生的一种鸭式类型。
最后一句话就是问题的答案。