46

我只是不知道,这有什么技术原因吗?为弱类型的语言实现编译器是否更难?它是什么?

4

9 回答 9

207

问题背后的前提有点狡猾。解释语言大多是鸭式的,这是不正确的。编译语言大多具有强类型是不正确的。类型系统是一种语言的属性。 编译与解释是实现的属性。

例子:

  • 编程语言 Scheme 是动态类型的(又名鸭子类型),它有许多解释实现,但也有一些优秀的本机代码编译器,包括 Larceny、Gambit 和 PLT Scheme(其中包括解释器和 JIT 编译器)无缝过渡)。

  • 编程语言 Haskell 是静态类型的;两个最著名的实现是解释器 HUGS 和编译器GHC。在编译为本机代码 (yhc) 和解释 (Helium) 之间,还有其他几个值得尊敬的实现。

  • 编程语言 Standard ML 是静态类型的,它有许多本机代码编译器,其中最好和最积极维护的编译器之一是MLton,但最有用的实现之一是解释器 Moscow ML

  • 编程语言 Objective Caml 是静态类型的。它只有一个实现(来自法国的 INRIA),但这个实现包括解释器和本机代码编译器。

  • 编程语言 Pascal 是静态类型的,但它在 1970 年代开始流行,因为 UCSD 构建了基于 P 代码解释器的出色实现。在后来的几年中,可以使用精细的本机代码编译器,例如用于 370 系列计算机的 IBM Pascal/VS 编译器。

  • 编程语言 C 是静态类型的,今天几乎所有的实现都是编译的,但在 1980 年代,我们这些有幸使用 Sabre C 的人正在使用解释器。

不过,您的问题背后有一些道理,因此您应该得到更深思熟虑的答案。事实是,动态类型语言似乎确实与解释实现相关。为什么会这样?

  • 许多新语言是由实现定义的。构建解释器比构建编译器更容易。动态检查类型比静态检查更容易。而且,如果您正在编写解释器,则静态类型检查几乎没有性能优势。

  • 除非您正在创建或调整一个非常灵活的多态类型系统,否则静态类型系统可能会妨碍程序员。但是,如果您正在编写解释器,一个原因可能是创建一个小型、轻量级的实现,它不会妨碍程序员。

  • 在某些解释型语言中,许多基本操作非常昂贵,以至于在运行时检查类型的额外开销并不重要。 PostScript 就是一个很好的例子:如果你要跑掉并立即栅格化 Bezier 曲线,你不会犹豫在这里或那里检查类型标签。

顺便提一下,请注意术语“强”和“弱”类型,因为它们没有普遍认可的技术含义。相比之下,静态类型意味着程序在执行之前会被检查,并且程序在启动之前可能会被拒绝。 动态类型意味着执行期间检查的类型,并且类型不佳的操作可能会导致程序停止或以其他方式在运行时发出错误信号。. 静态类型的一个主要原因是排除可能有这种“动态类型错误”的程序。(这是编写解释器的人通常对静态类型不太感兴趣的另一个原因;执行在类型检查之后立即发生,因此保证的区别和性质不那么明显。)

强类型一般意味着类型系统没有漏洞,而弱类型意味着类型系统可以被颠覆(使任何保证失效)。这些术语经常被错误地用于表示静态和动态类型。要看到区别,想想 C:该语言在编译时进行类型检查(静态类型),但有很多漏洞;您几乎可以将任何类型的值转换为相同大小的另一种类型——特别是,您可以自由地转换指针类型。Pascal 是一种旨在进行强类型化的语言,但众所周知的是有一个无法预料的漏洞:没有标签的变体记录。

随着时间的推移,强类型语言的实现经常会出现漏洞,通常使得运行时系统的一部分可以用高级语言实现。例如,Objective Caml 有一个被调用的函数Obj.magic,它具有简单地返回其参数的运行时效果,但在编译时它将任何类型的值转换为任何其他类型的值。我最喜欢的例子是 Modula-3,其设计者将他们的类型转换构造称为LOOPHOLE

总之:

  • 静态与动态是语言

  • 编译与解释是实现

  • 原则上,这两个选择可以并且是正交的,但是出于合理的技术原因,动态类型经常与解释相关联

于 2008-12-18T03:23:11.650 回答
10

您进行早期绑定(强类型)的原因是性能。使用早期绑定,您可以在编译时找到方法的位置,以便在运行时它已经知道它所在的位置。

但是,对于后期绑定,您必须去寻找一个看起来像客户端代码调用的方法的方法。当然,由于程序中有很多很多方法调用,这就是动态语言“慢”的原因。

但是可以肯定的是,您可以创建一种进行后期绑定的静态编译语言,这会否定静态编译的许多优点。

于 2008-12-18T01:32:05.443 回答
5

这很大程度上是因为编写和使用解释型语言的人更喜欢鸭式,而开发和使用编译型语言的人更喜欢强显式类型。(我认为对此的共识原因将是 90% 用于防止错误,10% 用于性能。)对于今天编写的大多数程序,速度差异将是微不足道的。Microsoft Word 已经在 p 代码(未编译)上运行了 - 什么 - 15 年了?

我能想到的最好的例子。经典的 Visual Basic (VB6/VBA/etc.) 相同的程序可以用 VB 编写,并以相同的结果和相当的速度运行,无论是编译还是解释。此外,您可以选择是否使用类型声明(实际上是变量声明)。大多数人更喜欢类型声明,通常是为了防止错误。我从来没有听说过或读过任何地方使用类型声明来提高速度。这至少可以追溯到硬件速度和容量的两个数量级。

谷歌最近受到了很多关注,因为他们为 javascript 开发了 JIT 编译器——这不需要对语言进行任何更改,也不需要程序员进行任何额外的考虑。在这种情况下,唯一的好处就是速度。

于 2008-12-18T02:21:22.117 回答
5

因为编译语言需要在编译时考虑使用的内存量。

当你看到类似的东西时:

int a

在 C++ 中,编译器会输出保留四个字节内存的代码,然后分配本地符号“a”以指向该内存。如果您有像 javascript 这样的无类型脚本语言,则解释器会在幕后分配所需的内存。你可以做:

var a = 10;  // a is probably a four byte int here
a = "hello world"; // now a is a 12 byte char array

这两条线之间发生了很多事情。解释器删除 a 处的内存,为字符分配新缓冲区,然后分配 a var 指向该新内存。在强类型语言中,没有解释器可以为您管理,因此编译器必须编写考虑到类型的指令。

int a = 10; // we now have four bytes on the stack.
a = "hello world"; // wtf? we cant push 12 bytes into a four byte variable! Throw an error!

所以编译器会停止编译该代码,这样 CPU 就不会盲目地将 12 个字节写入四字节缓冲区并造成痛苦。

编译器编写额外指令来处理类型的额外开销会显着降低语言速度,并消除 C++ 等语言的优势。

:)

-纳尔逊

编辑回应评论

我对Python不太了解,所以我不能说太多。但是松散的类型会大大减慢运行时间。解释器 (VM) 调用的每条指令都必须进行评估,并在必要时将 var 强制转换为预期的类型。如果你有:

mov a, 10
mov b, "34"
div a, b

然后解释器必须确保 a 是一个变量和一个数字,然后它必须在处理指令之前将 b 强制转换为一个数字。为虚拟机执行的每条指令加上开销,你手上就会一团糟:)

于 2008-12-18T01:31:42.357 回答
3

使用静态类型而不是鸭子类型基本上有两个原因:

  1. 静态错误检查。
  2. 表现

如果你有一种解释语言,那么就没有编译时间来进行静态错误检查。有一个优势。此外,如果您已经拥有解释器的开销,那么该语言已经不会用于任何对性能至关重要的事情,因此性能参数变得无关紧要。这就解释了为什么静态类型的解释语言很少见。

反过来说,duck 类型可以在很大程度上在静态类型语言中进行模拟,而不会完全放弃静态类型的好处。这可以通过以下任何方式完成:

  1. 模板。在这种情况下,如果您实例化模板的类型支持从模板中调用的所有方法,则您的代码可以编译并工作。否则它会给出编译时错误。这有点像编译时的鸭子类型。
  2. 反射。您尝试按名称调用方法,它要么正常工作,要么引发异常。
  3. 标记工会。这些基本上是其他类型的容器类,它们包含一些内存空间和一个描述当前包含的类型的字段。这些用于代数类型之类的东西。当一个方法被调用时,它要么工作要么抛出,这取决于当前包含的类型是否支持它。

这就解释了为什么很少有动态类型的编译语言。

于 2008-12-18T02:36:22.990 回答
2

我猜测具有动态(鸭子)类型的语言采用惰性评估,这受到惰性程序员的青睐,而惰性程序员不喜欢编写编译器;-)

于 2008-12-18T01:25:49.873 回答
2

弱类型的语言可以被编译,例如 Perl5 和 Lisp 的大多数版本都是编译语言。然而,编译的性能优势往往会丢失,因为语言运行时必须执行的大部分工作是确定动态变量在特定时间真正具有的类型。

以 Perl 中的以下代码为例:

$x=1;
$x="hello";
print $x;

编译器显然很难确定 $x 在给定时间点真正具有的类型。在打印声明时,需要做一些工作来解决这个问题。在静态类型语言中,类型是完全已知的,因此可以提高运行时的性能。

于 2008-12-18T01:33:14.990 回答
0

某些语言旨在在非异常条件下完美运行,而牺牲了它们在异常条件下遇到的可怕性能,因此非常强大的类型。其他人只是为了平衡它与额外的处理。

有时,除了打字之外,还有更多的事情在发挥作用。以 ActionScript 为例。3.0 引入了更强大的类型,但 ECMAScript 再次允许您在运行时根据需要修改类,并且 ActionScript 支持动态类。非常简洁,但是他们说动态类不应该在“标准”构建中使用这一事实意味着当您需要安全使用它时,这是一个禁忌。

于 2008-12-18T02:47:31.860 回答
0

一个推测:

在编译语言中,一个系统(编译器)可以看到执行强类型所需的所有代码。口译员通常一次只能看到一小部分程序,因此不能进行那种交叉检查。

但这并不是一个硬性规定——创建一种强类型的解释语言是很有可能的,但这会违背解释语言的那种“松散”的一般感觉。

于 2008-12-18T01:30:48.460 回答