与静态类型语言相比,动态类型语言的优点和局限性是什么?
另请参阅:对动态语言的热爱是什么(一个更具争议性的线程......)
与静态类型语言相比,动态类型语言的优点和局限性是什么?
另请参阅:对动态语言的热爱是什么(一个更具争议性的线程......)
解释器推断类型和类型转换的能力使开发时间更快,但它也可能引发运行时故障,而在静态类型语言中您无法在编译时捕获它们。但是最近(并且很长一段时间以来)社区中正在热烈讨论哪个更好(或者即使这总是正确的)。
对这个问题的一个很好的看法来自微软的 Erik Meijer 和 Peter Drayton 的《Static Typing Where possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages》 :
静态类型的拥护者认为静态类型的优点包括更早地检测编程错误(例如防止将整数添加到布尔值),更好的类型签名形式的文档(例如,在解析名称时合并参数的数量和类型),更多编译器优化的机会(例如,当接收器的确切类型静态已知时,用直接调用替换虚拟调用),提高运行时效率(例如,并非所有值都需要携带动态类型),以及更好的设计时开发人员体验(例如知道接收器的类型,IDE 可以显示所有适用成员的下拉菜单)。静态类型狂热者试图让我们相信“类型良好的程序不会出错”。虽然这听起来确实令人印象深刻,这是一个相当空洞的陈述。静态类型检查是程序运行时行为的编译时抽象,因此它必然只是部分合理且不完整。这意味着程序仍然可能由于类型检查器未跟踪的属性而出错,并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。静态类型检查是程序运行时行为的编译时抽象,因此它必然只是部分合理且不完整。这意味着程序仍然可能由于类型检查器未跟踪的属性而出错,并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。静态类型检查是程序运行时行为的编译时抽象,因此它必然只是部分合理且不完整。这意味着程序仍然可能由于类型检查器未跟踪的属性而出错,并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。因此它必然只是部分健全和不完整的。这意味着程序仍然可能由于类型检查器未跟踪的属性而出错,并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。因此它必然只是部分健全和不完整的。这意味着程序仍然可能由于类型检查器未跟踪的属性而出错,并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。并且有些程序虽然不会出错,但无法进行类型检查。正如“幻像类型”[11] 和“摇摆类型”[10] 等概念所证明的那样,使静态类型变得不那么部分和更完整的冲动导致类型系统变得过于复杂和奇异。这就像试图跑一场马拉松,腿上绑着一个球和链子,然后得意洋洋地大喊你几乎成功了,即使你在第一英里后跳了出来。
动态类型语言的拥护者认为静态类型过于死板,动态语言的柔软性使其非常适合用于具有变化或未知需求的原型系统,或者与其他不可预测的变化系统交互(数据和应用程序集成)。当然,动态类型语言对于处理真正动态的程序行为是必不可少的,例如方法拦截、动态加载、移动代码、运行时反射等。在所有关于脚本的论文之母 [16] 中,John Ousterhout 认为静态类型系统与动态类型的脚本语言相比,编程语言降低了代码的可重用性、更冗长、更不安全、更缺乏表现力。这个论点被许多动态类型脚本语言的支持者照搬。我们认为这是一个谬误,并且与声称声明式编程的本质是消除分配属于同一类别。或者正如 John Hughes 所说的 [8],通过省略特性来使语言更强大在逻辑上是不可能的。捍卫将所有类型检查延迟到运行时是一件好事的事实,是在玩鸵鸟策略,即应在开发过程中尽早发现错误。通过省略特性来使语言更强大在逻辑上是不可能的。捍卫将所有类型检查延迟到运行时是一件好事的事实,是在玩鸵鸟策略,即应在开发过程中尽早发现错误。通过省略特性来使语言更强大在逻辑上是不可能的。捍卫将所有类型检查延迟到运行时是一件好事的事实,是在玩鸵鸟策略,即应在开发过程中尽早发现错误。
静态类型系统试图以静态方式消除某些错误,在不运行程序的情况下检查程序并尝试在某些方面证明其合理性。一些类型系统能够比其他类型系统捕获更多的错误。例如,如果使用得当,C# 可以消除空指针异常,而 Java 则没有这种能力。Twelf 有一个类型系统,它实际上保证证明将终止,“解决”停止问题。
然而,没有类型系统是完美的。为了消除特定类别的错误,他们还必须拒绝某些违反规则的完全有效的程序。这就是为什么 Twelf 并没有真正解决停止问题,它只是通过抛出大量恰好以奇怪方式终止的完全有效的证明来避免它。同样,Java 的类型系统拒绝 Clojure 的PersistentVector
实现,因为它使用了异构数组。它在运行时工作,但类型系统无法验证它。
出于这个原因,大多数类型系统都提供“转义”,即覆盖静态检查器的方法。对于大多数语言,这些采用强制转换的形式,尽管有些(如 C# 和 Haskell)具有标记为“不安全”的完整模式。
主观上,我喜欢静态类型。正确实施(提示:不是Java),静态类型系统可以极大地帮助在错误使生产系统崩溃之前清除它们。动态类型语言往往需要更多的单元测试,这在最好的时候是乏味的。此外,静态类型语言可能具有某些在动态类型系统中不可能或不安全的特性(隐式转换浮现在脑海中)。这都是需求和主观品味的问题。我不会再用 Ruby 构建下一个 Eclipse,就像我不会尝试用 Assembly 编写备份脚本或使用 Java 修补内核一样。
哦,那些说“ x打字比y打字效率高 10 倍”的人简直是在冒烟。在许多情况下,动态类型可能“感觉”更快,但一旦你真正尝试让你的花哨的应用程序运行,它就会失去基础。同样,静态类型似乎是一个完美的安全网,但一看 Java 中一些更复杂的泛型类型定义,大多数开发人员都会眼花缭乱。即使有类型系统和生产力,也没有灵丹妙药。
最后一点:在比较静态类型和动态类型时,不要担心性能。V8 和 TraceMonkey 等现代 JIT 正在危险地接近静态语言性能。此外,Java 实际上编译为一种固有的动态中间语言这一事实应该暗示,在大多数情况下,动态类型并不是某些人认为的巨大的性能杀手。
嗯,两者都是非常非常非常非常被误解的,也是两个完全不同的东西。这不是相互排斥的。
静态类型是语言语法的限制。严格来说,静态类型语言可以说不是上下文无关的。一个简单的事实是,在不将所有数据简单地视为位向量的上下文无关语法中理智地表达一种语言变得不方便。静态类型系统是语言语法的一部分,如果有的话,它们只是比上下文无关语法限制更多,因此语法检查实际上是在源代码的两次传递中发生的。静态类型对应于类型论的数学概念,数学中的类型论只是限制了某些表达式的合法性。就像,我不能3 + [4,7]
在数学中说,这是因为它的类型论。
因此,静态类型不是从理论角度“防止错误”的方法,它们是语法的限制。确实,只要 +、3 和区间具有通常的集合理论定义,如果我们删除类型系统,就会3 + [4,7]
有一个非常明确的结果,那就是集合。“运行时类型错误”理论上不存在,类型系统的实际用途是防止对人类没有意义的操作。当然,操作仍然只是位的移位和操作。
对此的问题是,类型系统无法决定是否允许运行此类操作。例如,将所有可能的程序集精确地划分为那些会出现“类型错误”的程序,以及那些不会出现“类型错误”的程序。它只能做两件事:
1:证明程序中会发生类型错误
2:证明程序中不会发生类型错误
这似乎是我在自相矛盾。但是 C 或 Java 类型检查器所做的是将程序拒绝为“不合语法”,或者如果它不能在 2 处成功,则将其称为“类型错误”。它不能证明它们不会发生,这并不意味着它们不会发生,它只是意味着它无法证明它。一个没有类型错误的程序很可能被拒绝,因为它不能被编译器证明。一个简单的例子是if(1) a = 3; else a = "string";
,当然因为它总是正确的,else-branch 永远不会在程序中执行,也不会发生类型错误。但是它不能以一般的方式证明这些案例,所以它被拒绝了。这是许多静态类型语言的主要弱点,在保护您免受自己侵害时,您也必须在不需要它的情况下受到保护。
但是,与普遍认为的相反,也有静态类型语言按照原则 1 工作。他们简单地拒绝所有可以证明会导致类型错误的程序,并通过所有他们不能证明的程序。因此,它们可能允许程序中有类型错误,一个很好的例子是 Typed Racket,它是动态和静态类型的混合体。有些人会争辩说,在这个系统中你可以两全其美。
静态类型的另一个优点是类型在编译时是已知的,因此编译器可以使用它。如果我们在 Java 中使用"string" + "string"
or 3 + 3
,则文本中的两个+
标记最后都代表完全不同的操作和数据,编译器知道单独从类型中选择哪一个。
现在,我将在这里发表一个非常有争议的声明,但请耐心等待:“动态类型”不存在。
听起来很有争议,但确实如此,动态类型语言从理论角度来看是untyped。它们只是只有一种类型的静态类型语言。或者简单地说,它们是实际上由上下文无关语法在语法上生成的语言。
为什么他们没有类型?因为每个操作都定义并允许在每个操作符上,所以究竟什么是“运行时类型错误”?它来自一个理论示例,纯粹是副作用。如果doing print("string")
which prints a string是一个操作,那么也是length(3)
,前者有写入标准输出的副作用string
,后者简单error: function 'length' expects array as argument.
,就是这样。从理论的角度来看,没有动态类型语言这样的东西。它们没有类型
好吧,“动态类型”语言的明显优势是表达能力,类型系统只不过是表达能力的限制。一般来说,如果类型系统被忽略,那么具有类型系统的语言确实会为所有那些不允许的操作定义一个结果,结果对人类来说是没有意义的。许多语言在应用类型系统后失去了图灵完整性。
明显的缺点是可能发生的操作会产生对人类无意义的结果。为了防止这种情况,动态类型语言通常会重新定义这些操作,而不是产生无意义的结果,而是将其重新定义为产生写出错误的副作用,并可能完全停止程序。这根本不是“错误”,实际上,语言规范通常暗示这一点,从理论的角度来看,这与打印字符串一样多的语言行为。因此,类型系统迫使程序员对代码流进行推理,以确保不会发生这种情况。或者确实,有理由这样做在调试的某些点上发生也很方便,表明它根本不是“错误”,而是语言的一个明确定义的属性。实际上,大多数语言所拥有的“动态类型”的唯一残余就是防止被零除。这就是动态类型,没有类型,没有比零与所有其他数字不同的类型更多的类型。人们所说的“类型”只是数据的另一个属性,例如数组的长度或字符串的第一个字符。许多动态类型语言还允许您写出诸如"error: the first character of this string should be a 'z'"
.
另一件事是动态类型语言在运行时具有可用的类型,并且通常可以检查并处理它并从中做出决定。当然,理论上它与访问数组的第一个字符并查看它是什么没有什么不同。事实上,您可以制作自己的动态 C,只使用一种类型,如 long long int 并使用它的前 8 位来存储您的“类型”并相应地编写函数来检查它并执行浮点或整数加法。您有一种具有一种类型的静态类型语言,或者一种动态语言。
在实践中,这一切都表明,静态类型语言通常用于编写商业软件的上下文中,而动态类型语言倾向于用于解决某些问题和自动化某些任务的上下文中。用静态类型语言编写代码需要很长时间并且很麻烦,因为你不能做你知道会好的事情,但是类型系统仍然可以保护你免受你没有犯的错误。许多编码人员甚至没有意识到他们这样做是因为它在他们的系统中,但是当您使用静态语言进行编码时,您通常会绕过这样一个事实,即类型系统不会让您做不会出错的事情,因为它不能证明它不会出错。
正如我所指出的,“静态类型”通常意味着案例 2,在被证明无罪之前是有罪的。但是有些语言根本没有从类型理论派生出它们的类型系统,它们使用规则 1:在被证明有罪之前是无辜的,这可能是理想的混合体。所以,也许 Typed Racket 适合你。
另外,好吧,举一个更荒谬和极端的例子,我目前正在实现一种语言,其中“类型”确实是数组的第一个字符,它们是数据,“类型”的数据,“类型”本身一个类型和数据,唯一一个将自己作为类型的数据。类型不是有限的或静态有界的,但可以基于运行时信息生成新类型。
也许动态类型的最大“好处”是更浅的学习曲线。没有要学习的类型系统,也没有用于极端情况(例如类型约束)的非平凡语法。这使得更多人可以使用动态类型,并且对于许多无法使用复杂的静态类型系统的人来说是可行的。因此,动态类型在教育环境(例如 MIT 的 Scheme/Python)和非程序员的领域特定语言(例如Mathematica)中流行起来。动态语言也在竞争很少或没有竞争的领域(例如 Javascript)中流行起来。
最简洁的动态类型语言(例如 Perl、APL、J、K、Mathematica )是特定领域的,并且在它们设计的领域中比最简洁的通用静态类型语言(例如OCaml )要简洁得多.
动态类型的主要缺点是:
运行时类型错误。
要达到相同水平的正确性可能非常困难甚至几乎不可能,并且需要更多的测试。
没有经过编译器验证的文档。
由于依赖于复杂的优化,性能不佳(通常在运行时,但有时在编译时,例如斯大林方案)和不可预测的性能。
就个人而言,我是在动态语言上长大的,但除非没有其他可行的选择,否则我不会以 40 英尺的杆数作为专业人士接触它们。
来自 Artima 的打字:强与弱,静态与动态文章:
强类型可防止不匹配类型之间的混合操作。为了混合类型,您必须使用显式转换
弱类型意味着您可以混合类型而无需显式转换
在 Pascal Costanza 的论文Dynamic vs. Static Typing — A Pattern-Based Analysis (PDF) 中,他声称在某些情况下,静态类型比动态类型更容易出错。一些静态类型语言迫使您手动模拟动态类型以执行“正确的事情”。它在Lambda the Ultimate上进行了讨论。
这取决于上下文。有很多好处适用于动态类型系统以及强类型。我认为动态类型语言的流程更快。动态语言不受类属性和编译器思考代码中发生的事情的限制。你有一些自由。此外,动态语言通常更具表现力,并且代码更少,这很好。尽管如此,它更容易出错,这也是有问题的,并且更多地依赖于单元测试覆盖。它是具有动态语言的简单原型,但维护可能会成为噩梦。
静态类型系统的主要优点是 IDE 支持和代码的静态分析器。每次代码更改后,您都会对代码更有信心。有了这些工具,维护就很轻松了。
关于静态和动态语言有很多不同的东西。对我来说,主要区别在于动态语言中的变量没有固定类型。相反,类型与值相关联。因此,要执行的确切代码直到运行时才确定。
在早期或幼稚的实现中,这是一个巨大的性能拖累,但现代 JIT 非常接近通过优化静态编译器可以获得的最佳性能。(在某些边缘情况下,甚至比这更好)。
这一切都与工作的正确工具有关。100%的时间都不是更好。这两个系统都是人为创造的,都有缺陷。对不起,但我们很糟糕,制作完美的东西。
我喜欢动态类型,因为它不适合我,但是运行时错误可能会出现,这是我没有计划的。静态类型可以修复上述错误,但会让新手(使用类型化语言)程序员疯狂地尝试在常量字符和字符串之间进行转换。
静态类型: Java 和 Scala 等语言是静态类型的。
变量在代码中使用之前必须定义和初始化。
例如。诠释 x; x = 10;
System.out.println(x);
动态类型: Perl 是一种动态类型语言。
变量在代码中使用之前不需要初始化。
y=10;在后面的代码中使用这个变量