鸭子打字与静态打字有什么好处?

cod*_*Les 27 groovy duck-typing

我正在使用Groovy进行更多的研究和实验,并且我试图围绕在Groovy中实现事物的优点和缺点,我不能/不能用Java做.动态编程对我来说仍然只是一个概念,因为我已经深深沉浸在静态和强类型语言中.

Groovy给了我鸭子型的能力,但我无法真正看到它的价值.鸭子打字比静态打字更有效率?在我的代码练习中我可以做些什么来帮助我掌握它的好处?

我用Groovy问这个问题,但我知道它不一定是一个Groovy问题所以我欢迎来自每个代码阵营的答案.

Lyp*_*eus 16

很多关于鸭子打字的评论并没有真正证实这些说法.对于某种类型而言,"不必担心"是不可持续的维护或使应用程序可扩展.我真的有机会看到Grails在我的上一份合同中采取行动,真的非常有趣.每个人都对能够"创造应用程序"并开始行动的成果感到高兴 - 遗憾的是,它们都会在后端赶上你.

Groovy对我来说似乎是一样的.当然你可以编写非常简洁的代码,并且肯定在我们如何使用属性,集合等方面有一些不错的糖......但是不知道到底来回传递什么的成本变得越来越糟.在某些时候,你会挠头,想知道为什么该项目已经成为80%的测试和20%的工作.这里的教训是"较小"不会产生"更易读"的代码.对不起,伙计们,它的逻辑简单 - 你必须越直观地知道,然后理解代码的过程就越复杂.这就是为什么GUI多年来已经成为过度标志性的原因 - 肯定看起来很漂亮,但WTH正在发生并不总是显而易见的.

那个项目的人似乎有麻烦"扼杀"所学到的经验教训,但是当你有方法返回T类型的单个元素,T的数组,ErrorResult或null时,它变得相当明显.

然而,使用Groovy的一件事已经为我做了 - 令人敬畏的计费小时数!


Nic*_*ack 10

如果您使用Haskell,它具有令人难以置信的静态类型系统,那么静态类型没有任何问题.但是,如果您使用的Java和C++等语言具有非常严重的类型系统,那么鸭子打字肯定是一种改进.

想象一下,尝试在Java中使用像" map " 这样简单的东西(不,我不是指数据结构).即使是泛型也很难得到支持.

  • 哦好的.在Groovy中,collect():List doubled = x.collect {it*2}.Scala将其称为map()并具有强类型.我不知道Java的类型系统如何妨碍这一点.Java很快就会关闭,我确信Collections框架会得到collect/map. (2认同)

Ken*_*tle 9

鸭子打字瘫痪最现代的IDE的静态检查,可以在你输入时指出错误.有些人认为这是一个优势.我希望IDE/Compiler告诉我,我已经尽快制作了一个愚蠢的程序员技巧.

我最近最喜欢反对鸭子打字的论点来自Grails项目DTO:

class SimpleResults {
    def results
    def total
    def categories
}
Run Code Online (Sandbox Code Playgroud)

这里results原来是一样的东西Map<String, List<ComplexType>>,这只能通过下面的方法的线索称在不同的班级,直到你找到它的创建被发现.对于最终的好奇,totalList<ComplexType>s 的大小的总和,并且categories是大小Map

原来的开发人员可能已经清楚了,但是那个糟糕的维护人员(ME)失去了很多头发跟踪这个.


Jas*_*ker 7

在您使用它一段时间之前,看到鸭子打字的价值有点困难.一旦你习惯了它,你就会意识到你不必处理接口或者不必担心什么类型的东西是多少负担.


Cha*_*tin 7

接下来,哪个更好:EMACS还是vi?这是正在进行的宗教战争之一.

可以这样想:如果语言是静态类型的,任何正确的程序都是正确的.静态类型的作用是让编译器有足够的信息在编译时检测类型不匹配而不是运行时.如果你正在进行增量编程,这可能是一种烦恼,尽管(我坚持认为)如果你清楚地考虑你的程序它并不重要; 另一方面,如果你正在构建一个非常大的程序,比如操作系统或电话交换机,有数十或数百或数千人在上面工作,或者具有非常高的可靠性要求,那么让编译器能够为您检测一大类问题,而无需测试用例来运行正确的代码路径.

这并不是说动态类型是一个新的和不同的东西:例如,C是有效动态类型的,因为我总是可以将a转换foo*为a bar*.它只是意味着我作为C程序员的责任永远不会使用适当的代码,bar*当地址真正指向a时foo*.但是由于大型程序的问题,C开发了像lint(1)这样的工具,增强了它的类型系统,typedef最终在C++中开发了一个强类型的变体.(当然,C++反过来开发了强类型的方法,包括所有类型的演员表和泛型/模板以及RTTI.

但另一件事是 - 不要将"敏捷编程"与"动态语言"混为一谈. 敏捷编程是关于人们在项目中协同工作的方式:项目能否适应不断变化的需求以满足客户的需求,同时为程序员保持人性化的环境?它可以使用动态类型语言来完成,并且通常是因为它们可以更高效(例如,Ruby,Smalltalk),但它可以在C甚至汇编程序中成功完成.实际上,Rally Development甚至使用敏捷方法(特别是SCRUM)来进行营销和文档编制.

  • 在静态类型下,任何正确的程序都是正确的,这是不正确的.静态类型是运行时行为的保守近似.这就是为什么一些带有强制转换的程序仍然可以是类型正确的(通过保留类型检查器无法证明的不变量). (11认同)
  • 对不起,C的弱类型系统与动态类型不同.它甚至都不是很接近.没有后期绑定.像您的示例一样的强制转换指针会导致程序采用不同的底层结构导致错误,而不是功能. (6认同)

Mig*_*ing 5

恕我直言,当您遵守一些约定时,鸭子类型的优势就会放大,例如以一致的方式命名变量和方法。以Ken G为例,我认为它最好读:

class SimpleResults {
    def mapOfListResults
    def total
    def categories
}
Run Code Online (Sandbox Code Playgroud)

假设您在名为“calculateRating(A,B)”的某个操作上定义了一个契约,其中 A 和 B 遵守另一个契约。在伪代码中,它将读取:

Long calculateRating(A someObj, B, otherObj) {

   //some fake algorithm here:
   if(someObj.doStuff('foo') > otherObj.doStuff('bar')) return someObj.calcRating());
   else return otherObj.calcRating();

}
Run Code Online (Sandbox Code Playgroud)

如果你想在 Java 中实现它,A 和 B 都必须实现某种接口,读取如下内容:

public interface MyService {
    public int doStuff(String input);
}
Run Code Online (Sandbox Code Playgroud)

此外,如果你想概括你的评分计算合同(假设你有另一种评分计算算法),你还必须创建一个接口:

public long calculateRating(MyService A, MyServiceB);
Run Code Online (Sandbox Code Playgroud)

使用鸭子类型,您可以放弃接口而只依赖运行时,A 和 B 都会正确响应您的doStuff()调用。不需要特定的合同定义。这对你有用,但也可能对你不利。

缺点是您必须格外小心,以确保您的代码在其他人更改时不会中断(即,其他人必须了解方法名称和参数的隐式约定)。

请注意,这在 Java 中尤为严重,Java 中的语法并不像它可能的那样简洁(例如与Scala相比)。一个反例是Lift framework,他们说该框架的 SLOC 计数类似于Rails,但测试代码的行数较少,因为它们不需要在测试中实现类型检查。


suk*_*007 5

有了TDD + 100% 代码覆盖率+ IDE 工具来不断地运行我的测试,我觉得不再需要静态类型了。没有强类型,我的单元测试变得如此简单(只需使用 Maps 来创建模拟对象)。特别是,当您使用泛型时,您可以看到不同之处:

//Static typing 
Map<String,List<Class1<Class2>>> someMap = [:] as HashMap<String,List<Class1<Class2>>>
Run Code Online (Sandbox Code Playgroud)

对比

//Dynamic typing
def someMap = [:]   
Run Code Online (Sandbox Code Playgroud)