59

我非常喜欢的一件事是阅读不同的编程语言。目前,我正在学习 Scala,但这并不意味着我对 Groovy、Clojure、Python 等不感兴趣。所有这些语言都具有独特的外观和感觉以及一些特征。在 Clojure 的情况下,我不理解这些设计决策之一。据我所知,Clojure 非常强调它的函数范式,并且几乎迫使您尽可能使用不可变的“变量”。因此,如果一半的值是不可变的,为什么语言是动态类型的?

Clojure 网站说:

首先,Clojure 是动态的。这意味着 Clojure 程序不仅仅是您编译和运行的东西,而是您可以与之交互的东西。

嗯,这听起来很奇怪。如果一个程序被编译,你就不能再改变它了。当然,您可以与它“交互”,这就是 UI 的用途,但该网站当然并不意味着整洁的“动态”GUI。

Clojure 如何从动态类型中受益

我指的是 Clojure 的特例,而不是动态类型的一般优势。

动态类型系统如何帮助改进函数式编程

再一次,我知道不泄漏“int a”的乐趣;到处都是源代码,但类型推断可以减轻很多痛苦。因此,我只想知道动态类型如何支持函数式语言的概念。

4

5 回答 5

26

如果一个程序被编译,你就不能再改变它了。

这是错误的。在基于图像的系统中,如 Lisp(Clojure 可以看作是一种 Lisp 方言)和 Smalltalk,您可以更改编译环境。使用这种语言进行开发通常意味着在正在运行的系统上工作,添加和更改函数定义、宏定义、参数等(添加意味着编译和加载到映像中)。

这有很多好处。一方面,所有工具都可以直接与程序交互,无需猜测系统的行为。您也没有任何长时间的编译暂停,因为每个编译单元都非常小(重新编译所有内容非常罕见)。NASA JPL 曾经在数十万公里外的太空探测器上纠正了一个正在运行的 Lisp 系统。

对于这样的系统,在运行时提供类型信息是很自然的(这就是动态类型的含义)。当然,没有什么能阻止您在编译时进行类型推断和类型检查。这些概念是正交的。现代 Lisp 实现通常可以两者兼得。

于 2011-02-24T23:26:13.490 回答
18

首先,Clojure 是一个 Lisp,而 Lisp 传统上一直是动态类型的。

其次,正如您引用的摘录所说,Clojure 是一种动态语言。这意味着,除其他外,您可以在运行时定义新函数,在运行时评估任意代码等等。在静态类型语言中,所有这些事情都很难或不可能做到(无需到处抹灰)。

另一个原因是宏可能会使调试类型错误变得非常复杂。我想为宏生成的代码产生的类型错误生成有意义的错误消息对于编译器来说将是一项艰巨的任务。

于 2011-02-24T21:05:24.403 回答
15

我同意,纯函数式语言仍然可以具有交互式读取-评估-打印-循环,并且可以更轻松地进行类型推断。我假设 Clojure 想通过成为“jvm 的 lisp”来吸引 lisp 程序员,并选择像其他 lisp 一样动态。另一个因素是类型系统需要设计为语言的第一步,语言实现者跳过这一步会更快。

于 2011-02-24T21:18:14.370 回答
7

(我改写原来的答案,因为它产生了太多的误解)

保持 Clojure(和任何 Lisp)动态类型的原因之一是简化宏的创建。简而言之,宏处理抽象语法树(AST),它可以包含许多不同类型的节点(通常是任何对象)。理论上,可以制作完整的静态类型宏系统,但在实践中,此类系统通常受到限制且分布稀疏。请参阅下面的示例和线程中的扩展讨论。


编辑 2020:哇,从我发布这个答案的时间已经过去了 9 年,人们仍然添加评论。我们都留下了怎样的遗产!

有些人在评论中指出,拥有静态类型语言并不会阻止您将代码表达为数据结构。而且,严格来说,这是真的——联合类型允许表达任何复杂的数据结构,包括语言的语法。然而,我声称要表达语法,你必须要么降低表达能力,要么使用如此广泛的联合,以至于你失去了静态类型的所有优势。为了证明这一说法,我将使用另一种语言——Julia。

Julia 是可选类型的——您可以将任何函数或结构字段限制为具有特定类型,Julia 会检查它。Expr该语言支持 AST 作为使用和Symbol类型的一等公民。表达式定义如下所示:

struct Expr
  head::Symbol
  args::Vector{Any}
end

表达式由一个始终是符号的头和可以具有任何类型的参数列表组成。Julia 还支持 specialUnion可以将参数限制为特定类型,例如Symbols 和 other Exprs:

struct Expr
  head::Symbol
  args::Vector{Union{Symbol, Expr}}
end

这足以表达例如:(x + y)

dump(:(x + y))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Symbol x
    3: Symbol y

但是 Julia 还支持许多其他类型的表达式。一个明显且有用的示例是文字:

:(x + 1)

此外,您可以手动使用插值或构造表达式将任何对象放入 AST:

obj = create_some_object()

ex1 = :(x + $objs)
ex2 = Expr(:+, :x, obj)

这些示例不仅仅是一个有趣的实验,它们在实际代码中被积极使用,尤其是在宏中。因此,您不能将表达式参数限制为特定的类型联合 - 表达式可以包含任何值。

当然,在设计新语言时,您可以对其施加任何限制。也许,限制Expr为仅包含Symbol,Expr和 some Literals 在某些情况下会很有用。但这违背了 Julia 和 Clojure 的简单性和灵活性原则,并且会显着降低宏的有用性。

于 2011-02-24T21:03:43.930 回答
4

因为这正是世界/市场所需要的。建造已经建造的东西毫无意义。

我听说 JVM 已经有了静态类型语言;)

于 2011-02-25T00:52:59.420 回答