关于类型系统:为什么动态打字经常与解释语言相关联?

Why Is Dynamic Typing So Often Associated with Interpreted Languages?

简单的问题是:我做了很多编程(专业和个人),比如C++语言和Java语言,还有Python/JavaScript的解释语言。我个人发现,当我用静态类型语言编程时,我的代码几乎总是更健壮的。但是,我遇到的几乎所有解释语言都使用动态类型(PHP、Perl、Python等)。我知道为什么编译语言使用静态类型(大多数时候),但我不知道在解释语言设计中对静态类型的厌恶。

为什么是陡峭的断开?它是解释语言的一部分吗?OOP?


有趣的问题。顺便说一句,我是phc(compiler for php)的作者/维护者,我正在攻读动态语言编译器的博士学位,所以我希望能提供一些见解。

我认为这里有一个错误的假设。PHP、Perl、Python、Ruby、Lua等的作者没有设计"解释语言",他们设计了动态语言,并使用解释程序实现它们。他们这样做是因为口译员比编译器更容易编写。

Java的第一个实现被解释,它是静态类型的语言。静态语言确实存在解释器:haskell和ocaml都有解释器,以前C语言有一个流行的解释器,但那是很久以前的事了。它们之所以流行,是因为它们允许repl,这可以使开发更容易。

也就是说,正如您所期望的,动态语言社区中存在对静态类型的厌恶。他们认为,C、C++和Java所提供的静态类型系统是冗长的,不值得付出努力。我认为我在某种程度上同意这一点。在Python编程比C++更有趣。

要解决其他人的问题:

  • Dlamblin说:"我从来没有强烈地感觉到编译与解释之间有什么特别之处,它们暗示了动态类型与静态类型之间的关系。"好吧,你错得很严重。动态语言的编译非常困难。主要考虑的是eval语句,在javascript和Ruby中广泛使用。phc提前编译php,但是我们仍然需要一个运行时解释器来处理evals。eval也不能在优化编译器中进行静态分析,尽管如果不需要稳定性,有一种很酷的技术。

  • 对于damblin对andrew hare的响应:当然,您可以在解释器中执行静态分析,并在运行时之前发现错误,这正是haskell的ghci所做的。我希望函数语言中使用的解释器样式需要这样做。德兰布林当然有权说,分析不是解释的一部分。

  • 安德鲁·黑尔的回答是以提问者错误的假设为前提的,同样的,事情也有错误的方向。然而,他提出了一个有趣的问题:"动态语言的静态分析有多困难?"非常困难。基本上,你将获得一个博士学位来描述它是如何工作的,这正是我正在做的。另请参见上一点。

  • 到目前为止,最正确的答案是IvoWetzel。但是,他描述的点可以在编译器的运行时处理,并且有许多针对Lisp和具有这种动态绑定的方案的编译器。但是,是的,这很棘手。


我认为这是因为解释语言的本质,它们希望是动态的,这样您就可以在运行时更改内容。因此,在下一行代码被删除后,编译器永远不会确切知道程序的状态。

想象一下下面的场景(在python中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import random
foo = 1

def doSomeStuffWithFoo():
    global foo
    foo = random.randint(0, 1)

def asign():
    global foo
    if foo == 1:
        return 20
    else:
        return"Test"


def toBeStaticallyAnalyzed():
    myValue = asign()

    # A"Compiler" may throw an error here because foo == 0, but at runtime foo maybe 1, so the compiler would be wrong with its assumption
    myValue += 20


doSomeStuffWithFoo() # Foo could be 1 or 0 now... or 4 ;)
toBeStaticallyAnalyzed()

如您所希望看到的,编译器在这种情况下没有任何意义。准确地说,它可以警告你,"我的价值"可能不是一个数字。但是在javascript中,如果"myvalue"是一个字符串,那么20也会被隐式转换为字符串,因此不会发生错误。所以你可能会收到成千上万个无用的警告,我不认为这是编译器的意图。

灵活性总是有代价的,你需要更深入地了解你的程序,或者更仔细地编程,换句话说,你就是上面这种情况下的编译器。

那么你的编译器解决方案呢?-用"try:except":修复它)


解释语言使用动态类型,因为不存在执行静态分析的编译步骤。编译语言在编译时进行静态分析,这意味着任何类型错误都会在开发人员工作时报告给他们。

如果您认为静态类型语言具有在执行上下文之外强制执行类型规则的编译器,则更容易理解。解释语言从不进行静态分析,因此类型规则必须由解释器在执行上下文中强制执行。


编译器+静态类型=高效的机器代码编译器+动态类型=效率低下的机器代码

考虑以下伪代码:

1
2
3
function foo(a, b) {
    return a+b
}

静态语言将能够(通过声明或推理)知道a和b是整数,并将编译为

1
%reg = addi a,b

或者类似的东西。

动态语言的编译器必须将代码发送到1。检查A和B的类型2。处理每个案例或案例组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%reg1 = typeof a
beq %reg1, int, a_int_case
beq %reg1, float, a_float_case
beq %reg1, string, a_string_case

label a_int_case
%reg1 = typeof b
beq %reg1, int, a_int_b_int_case
beq %reg1, float, a_int_b_float_case
beq %reg1, string, a_int_b_string_case

label a_int_b_int_case
%out = addi a,b
goto done

label a_int_b_float_case
%tmp = mkfloat a
%out = addf %tmp,b
goto done

... Etc. I can't finish

虽然您可以生成比这更智能的机器代码,但是您不能帮助生成大量的代码——这使得编译对于动态语言来说不是一个主要的胜利。

既然口译员更容易写,而编译对你没什么好处,为什么不写一个口译员呢?

(just-in-time编译器实际上有类型信息,可以直接编译到单个语句。它们实际上比静态类型的系统拥有更多的信息,理论上可以做得更好。所有的汇编程序都是模拟的;任何与真实机器上运行的真实代码相似的地方都是完全巧合的。)


可能是因为我的一种主要解释语言是Perl,而我的一种编译语言是Objective-C,但我从未强烈感觉到编译与解释之间有什么特别之处,这表明动态类型优于静态类型。

我认为很明显,双方都在考虑另一方,"这有一些好处。"在一些应用程序中,获得一些动态类型的灵活性更容易,而维护静态类型和强制类型的东西更容易。

不过,我不同意安德鲁·黑尔的解释。虽然纯解释语言必须添加一个预处理步骤,因此在执行静态类型错误之前不需要纯解释来警告程序员,但它并不排除在运行时抛出类型错误。因此,缺少编译并不意味着不会发生静态类型检查。但是,由于在运行时获取类型错误不如在编译时或在飞行前检查时获取类型错误有用,因此我可以看到在这种情况下静态类型的"优势"似乎更令人讨厌,因此为了支持动态类型带来的优势而被丢弃。

如果您从一开始就知道您喜欢保持类型静态,因为个人而言,这样可以编写更好的可维护代码,并且您正在设计解释语言,那么没有什么可以阻止您将语言设计为静态类型。

引用解释语言wiki文章"理论上,任何语言都可以被编译或解释,因此这种指定的应用纯粹是因为常见的实现实践,而不是语言的一些基本属性。"有一篇关于打字的不错的维基文章。


动态类型的解释语言使您在编程过程中有更多的自由。它允许元编程是可行的。它允许在运行时创建变量。允许在运行时的任何给定时间创建匿名哈希和匿名数组,而无需事先声明任何内容。它允许将未确定的信息输入哈希,而无需预先声明所有键。可以使用未确定的随机输入创建子例程。您还可以提供可以动态运行的程序代码。解释语言释放了限制一般编程的链。您只能使用静态类型语言在源文件中键入内容。在动态类型语言中,您可以用更少的资源做更多的事情。

今天制造的大多数机器人更多地处理解释语言,因为信息需要在运行时确定,而新的变量需要在运行时存储这些信息。机器学习是基于被解释的信息。作为人类,我们自己就是解释者,这就是为什么机器人是这样设计的。未来真的是可以解释的。当然,您需要静态类型语言来构建解释器,所以静态类型语言永远不会消失,除非将来解释器是在汇编代码中构建的。如今,大多数口译员都是建立在静态类型语言之上的。

解释语言在动态环境中表现出色。如果您可以在运行时解释新的代码/信息,那么为什么不解释呢?如果您真的擅长动态编程,那么您可以创建代码来创建变量和散列,而无需输入任何内容。如果使用大量数据,可以大幅减少行的数量。您可以使用数据转储程序打印出所有信息,因为解释语言通常在运行时跟踪变量的类型,从而允许这样做。你不能在BaseB++中这样做。唯一的时间是C++和C知道在编译时发生了什么。在那之后,除非你自己实现了一些东西,否则你自己就可以了。

这些天,尤其是在动态环境中工作时,谁希望如此多地与源文件绑定在一起。你所做的一切都限制了你的潜能。一旦你的脖子深入到动态解释的代码中,回到任何静态类型的语言中,你会发现很难屏蔽你的代码,因为你仍在以无限的心态思考。你的大脑需要重新回到被限制在源文件中输入的内容上。

在编程风格方面:静态类型的代码产生静态结果。动态类型代码产生动态或静态结果。

如果您要编程的东西除了已知的之外永远不会改变其他行为,那么静态类型语言就非常适合这样做。如果处理动态行为,那么动态类型语言更适合这些情况。一切主要取决于形势。

每一种语言都有其起伏。只是要明智地选择。


我认为静态类型可以让编译器更容易,这也是编译语言中存在静态类型的主要原因(如果不仅仅如此)。

对于解释语言,更容易假定变量没有类型(只有值有),因为它们被认为不是必须放在堆中的数据的放置器,而是浮动在堆中某处的数据的标签。

如果程序员需要,他总是可以断言变量持有给定类型的值(例如在赋值时)。没有理由把它构建成语言。当然,这与您对编译语言的控制不同。

您可能需要使用某种语言来显式地声明每个变量的类型,但是如果您不这样做,那么使用静态类型,需要程序员精心设计的复杂泛型类型,这样做就容易多了。

另一方面。您知道任何动态类型编译(静态,而不是JIT)语言吗?