我不知道这是否有技术原因?使用弱类型语言实现编译器是否更加困难?这是什么情况?
我不知道这是否有技术原因?使用弱类型语言实现编译器是否更加困难?这是什么情况?
问题的前提有点不可信。解释型语言并非大多数为鸭子类型,编译型语言也不大多是强类型。 类型系统是一种语言特性,编译还是解释是一个实现的特性。
例如:
编程语言Scheme是动态类型(又称鸭式类型),它有许多几十个解释器实现,但也有一些良好的本地代码编译器,包括Larceny,Gambit和PLT Scheme(其中包括同时具备解释器和JIT编译器的过渡)。
编程语言Haskell是静态类型的;最著名的两个实现是解释器HUGS和编译器GHC。还有其他几个光荣的实现,分为编译成本机代码(yhc)和解释(Helium)。
编程语言Standard ML是静态类型的,它有许多本地代码编译器,其中最好且最活跃的之一是MLton,但最有用的实现之一是解释器Moscow ML。
编程语言Objective Caml是静态类型的。它只带有一个实现(来自法国INRIA),但此实现包括同时具备解释器和本地代码编译器。
编程语言Pascal是静态类型的,但由于在UCSD构建的优秀实现基于P-code解释器而变得流行。后来可用了更好的本地代码编译器,例如IBM Pascal/VS compiler for the 370 series of computers。
编程语言C是静态类型的,今天几乎所有实现都是编译的,但在1980年代,我们运气好的使用Saber C时正在使用解释器。
尽管你的问题有些言过其实,但是它确实有一定的道理,因此你应该得到一个更为深思熟虑的答案。事实是,动态类型语言似乎与解释执行的实现相关联。为什么会这样呢?
许多新语言是通过实现定义的。构建解释器比构建编译器更容易。在运行时动态检查类型比静态检查类型更容易。如果你正在编写解释器,则静态类型检查很少有性能优势。
除非你正在创建或调整一个非常灵活的多态类型系统,否则静态类型系统可能会妨碍程序员的工作。但是,如果你正在编写解释器,一个原因可能是要创建一个小型、轻量级的实现,以避免妨碍程序员的工作。
在一些解释型语言中,许多基本操作非常昂贵,以至于在运行时检查类型的额外开销并不重要。PostScript就是一个很好的例子: 如果你想随时随地运行和光栅化贝塞尔曲线,那么在这里或那里检查类型标记就没有问题。
顺便提一句,请谨慎使用“强类型”和“弱类型”这些术语,因为它们没有普遍认可的技术含义。相比之下,“静态类型”意味着程序在执行之前被检查,程序可能会在启动之前被拒绝。 “动态类型”意味着在执行期间检查值的类型,并且不良类型操作可能会导致程序停止或以其他方式在运行时发生错误。静态类型的主要原因是排除可能具有此类“动态类型错误”的程序。(这也是编写解释器的人对静态类型不太感兴趣的另一个原因; 在类型检查之后立即执行,因此区分和保证的性质并不明显。)
强类型通常意味着类型系统中没有漏洞,而弱类型意味着类型系统可以被破坏(从而使任何保证失效)。这些术语经常被错误地用来表示静态和动态类型。Obj.magic
的函数,在运行时产生的效果只是简单地返回其参数,但在编译时它将任何类型的值转换为任何其他类型之一。我最喜欢的例子是 Modula-3,其设计者称其类型转换结构为 LOOPHOLE
。静态 vs 动态是语言。
编译 vs 解释是实现。
原则上,这两个选择可以和被正交地做出,但由于技术上的充分理由,动态类型通常与解释相关联。
进行早期绑定(强类型)的原因是性能。 通过早期绑定,您可以在编译时找到方法的位置,因此在运行时它已经知道它的位置。
然而,使用晚期绑定,您必须搜索一个看起来像客户端代码调用的方法。 当然,对于许多程序中的方法调用,这就是动态语言“缓慢”的原因。
但是,您可以创建一个静态编译的语言,并进行后期绑定,这将抵消许多静态编译的优势。
编译型语言在编译时需要考虑使用的内存量。
当你看到像这样的内容:
int a
var a = 10; // a is probably a four byte int here
a = "hello world"; // now a is a 12 byte char array
在这两行代码之间发生了很多事情。解释器删除了变量a的内存,为字符分配了新的缓冲区,然后将a变量指向了那块新的内存。在强类型语言中,没有解释器来帮你管理这些,因此编译器必须编写能考虑到类型的指令。
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!
mov a, 10
mov b, "34"
div a, b
然后解释器必须确保a是一个变量和数字,然后它必须在处理指令之前将b强制转换为数字。如果VM执行每个指令都添加这样的开销,那么你就会手忙脚乱 :)
x = 12 + y
,那么 x
立即被绑定到一个thunk上,但是该thunk会被惰性地(稍后)求值。至少这是我对Haskell中惰性求值工作方式的直觉理解。 - Giorgio弱类型语言也可以编译,例如Perl5和大多数Lisp版本都是编译语言。然而,编译带来的性能优势通常会因为语言运行时需要执行的大量工作与确定特定时间内动态变量的类型有关而丢失。
以Perl中的以下代码为例:
$x=1;
$x="hello";
print $x;
有些编程语言旨在在非异常条件下运行完美,但这是以在异常条件下运行时遇到可怕的性能问题为代价的,因此需要非常强的类型。其他语言则是通过额外的处理来平衡它。
有时候,比起类型,还有更多的因素在发挥作用。以ActionScript为例,3.0引入了更强的类型,但ECMAScript又使你能够在运行时随意修改类,而ActionScript支持动态类。非常棒,但事实上,他们声称不应该在“标准”构建中使用动态类,这意味着当你需要保险时,这是不可取的。