我记得在Clojure for Lisp Programmers 视频中重复过的一个说法是,早期 Lisp,尤其是 Common Lisp 的一个很大的弱点是,太多地与 Lisps 的列表结构,尤其是cons
单元格结合在一起。您可以在链接视频的第 25 分钟处找到一次这种说法,但我确信我记得在该系列的其他地方听到过这种说法。重要的是,在视频的同一点,我们看到了这张幻灯片,它向我们展示了 Clojure 有许多其他的数据结构,而不仅仅是老式的 Lispy 列表:
这让我很困扰。我对 Lisp 的了解非常有限,但我一直被告知,其传奇的元可编程性的一个关键要素是,一切——是的,一切——都是一个列表,而且这就是防止我们在尝试对其他语言进行元编程时遇到的那种错误的原因。这是否表明通过添加新的一流数据结构,Clojure 降低了其同音性,从而使其比其他 Lisp(例如 Common Lisp)更难进行元编程?
4 回答
是不是一切——是的,一切——都是一个清单
这对 Lisp 来说从来都不是真的
CL-USER 1 > (defun what-is-it? (thing)
(format t "~%~s is of type ~a.~%" thing (type-of thing))
(format t "It is ~:[not ~;~]a list.~%" (listp thing))
(values))
WHAT-IS-IT?
CL-USER 2 > (what-is-it? "hello world")
"hello world" is of type SIMPLE-TEXT-STRING.
It is not a list.
CL-USER 3 > (what-is-it? #2a((0 1) (2 3)))
#2A((0 1) (2 3)) is of type (SIMPLE-ARRAY T (2 2)).
It is not a list.
CL-USER 4 > (defstruct foo bar baz)
FOO
CL-USER 5 > (what-is-it? #S(foo :bar oops :baz zoom))
#S(FOO :BAR OOPS :BAZ ZOOM) is of type FOO.
It is not a list.
CL-USER 6 > (what-is-it? 23749287349723/840283423)
23749287349723/840283423 is of type RATIO.
It is not a list.
由于 Lisp 是一种可编程编程语言,我们可以为非列表数据类型添加外部表示:
为 FRAME 类添加原始符号。
CL-USER 10 > (defclass frame () (slots))
#<STANDARD-CLASS FRAME 4210359BEB>
打印机:
CL-USER 11 > (defmethod print-object ((o frame) stream)
(format stream "[~{~A~^ ~}]"
(when (and (slot-boundp o 'slots)
(slot-value o 'slots))
(slot-value o 'slots))))
#<STANDARD-METHOD PRINT-OBJECT NIL (FRAME T) 40200011C3>
读者:
CL-USER 12 > (set-macro-character
#\[
(lambda (stream char)
(let ((slots (read-delimited-list #\] stream))
(o (make-instance 'frame)))
(when slots
(setf (slot-value o 'slots) slots))
o)))
T
CL-USER 13 > (set-syntax-from-char #\] #\))
T
现在我们可以读取/打印这些对象:
CL-USER 14 > [a b]
[A B]
CL-USER 15 > (what-is-it? [a b])
[A B] is of type FRAME.
It is not a list.
让我们把“同音性”这个词分开。
- homo-,意思是“相同的”
- 标志性的,这里的意思是“代表”
- -ity,意思是“具有这种属性的东西”
使宏成为可能(或至少更容易)的质量是语言本身用您用来表示其他对象的相同数据结构表示。请注意,这个词不是“单一主义”或“一切都是列表”。由于 Clojure 使用相同的数据结构来描述其语法,就像它在运行时描述其他值一样,因此没有理由声称它不是同形的。
有趣的是,我可能会说 Clojure 最不具有同音性的方式之一是它实际上与旧 lisps 共享的一个特性:它使用符号来表示源代码。在其他 lisps 中,这是非常自然的,因为符号用于许多其他事情。但是,Clojure 有关键字,它们更常用于在运行时为数据命名。符号很少使用;我什至可以说它们的主要用途是表示源代码元素!但是 Clojure 用来表示源代码元素的所有其他特性也经常用于表示其他事物:序列、向量、映射、字符串、数字、关键字、偶尔的集合,以及可能我没有想到的其他一些小众东西现在。
在 Clojure 中,除了列表之外,某些语言结构所需的额外数据结构中唯一的一个是向量,它们位于众所周知的位置,例如围绕函数的参数序列,或在符号/表达式对中一个let
。它们都可以用于数据字面量,但与总是在列表中的函数调用和宏调用相比,数据字面量在编写 Clojure 宏时不太常见。
我不知道 Clojure 中有什么东西会使编写宏比在 Common Lisp 中更困难,并且有一些与 Clojure 不同的特性可以使它更容易一些,例如默认情况下 Clojure 反引号表达式将命名空间的行为-qualify 符号,这通常是您想要防止意外“捕获”名称的方法。
正如之前的回答者指出的那样,“同音性”仅意味着著名的“code
是data
”:一种编程语言的代码由该语言的数据结构 100% 组成,因此很容易使用通常用于操作数据结构的语言。
由于 Clojure 数据结构包括列表、向量、映射......(您列出的那些)并且由于 Clojure 代码由列表、向量、映射组成...... Clojure 代码是同形的。
Common Lisp 和其他 lisp 也是如此。
Common Lisp 还包含列表、向量、哈希表等。然而,Common Lisp 的代码比 Clojure 更严格地坚持使用列表(函数参数在参数列表中,而不是像在 Clojure 中的向量中)。因此,您可以更一致地使用函数来操作用于元编程目的的列表(宏)。
这是否比 Clojure “更”谐音是一个哲学问题。但与 Clojure 相比,您绝对需要一组更小的函数来操作代码。