Clojure 是否比其他 lisp 更少谐音?
Is Clojure less homoiconic than other lisps?
我记得在 Clojure for Lisp Programmers videos 中重复的一个说法是,早期的 Lisp,特别是 Common Lisp 的一个很大的弱点是太多地与 Lisp 的列表结构结合,特别是 cons
细胞。您可以在链接视频的第 25 分钟标记处找到一次这种说法,但我确信我记得在该系列的其他地方听到过。重要的是,在视频的同一点,我们看到了这张幻灯片,它向我们展示了 Clojure 除了老派的 Lispy 列表之外还有许多其他数据结构:
这让我很烦恼。我对 Lisp 的了解非常有限,但我一直被告知其传奇的元可编程性的一个关键要素是一切 - 是的,一切 - 都是一个列表,而且 this is what prevents the sort of errors that we get when trying to metaprogram other languages。这是否表明通过添加新的 first-class 数据结构,Clojure 降低了其同象性,从而使其比其他 Lisp(例如 Common Lisp)更难进行元编程?
在 Clojure 中,除了列表之外,某些语言构造所需的额外数据结构中唯一的一个是向量,并且它们位于众所周知的位置,例如围绕函数的参数序列,或者在symbol/expression对一个let
。所有这些都可用于数据文字,但与始终在列表中的函数调用和宏调用相比,在编写 Clojure 宏时,数据文字不太常见。
我不知道在 Clojure 中有什么使得编写宏比在 Common Lisp 中更难,并且有一些不同于 Clojure 的特性可以使它更容易一些,例如 Clojure 反引号的行为默认情况下,表达式将命名空间限定符号,这通常是您要防止 'capturing' 意外命名的原因。
让我们把“同像性”这个词分开。
- homo-,意思是“相同”
- iconic,这里的意思类似于“由
代表”
- -ity,意思是“有这样的东西 属性”
使宏成为可能(或至少更容易)的质量是语言本身使用与您用来表示其他对象相同的数据结构来表示。请注意,这个词不是“单一主义”,也不是“一切都是清单”。由于 Clojure 在运行时使用相同的数据结构来描述其语法,因为它在描述其他值时也使用相同的数据结构,因此没有理由声称它不是谐音。
有趣的是,我可能会说 Clojure 最不具有同形异象的方式之一是它实际上与旧的 lisps 共享的一个特性:它使用符号来表示源代码。在其他 lisp 中,这是很自然的,因为符号用于很多其他事情。不过,Clojure 有关键字,它们更常用于在运行时为数据命名。很少使用符号;我什至可以说它们的主要用途是表示源代码元素!但是 Clojure 用来表示源代码元素的所有其他特性也经常被用来表示其他东西:序列、向量、映射、字符串、数字、关键字、偶尔的集合,以及可能其他一些我没有想到的小众东西现在。
is that everything - yes, everything - is a list
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 添加原始符号 class。
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>
reader:
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
现在我们可以read/print这些对象:
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.
正如前面的回答者所指出的,“同像性”无非就是著名的“code
is data
”:一种编程语言的代码由该语言的 100% 数据结构组成语言,因此可以使用通常用于操作数据结构的语言中的函数轻松构建和操作。
由于 Clojure 数据结构包括列表、向量、映射...(您列出的那些)并且由于 Clojure 代码由列表、向量、映射组成...Clojure 代码是同构的。
Common Lisp 和其他 lisp 也是如此。
Common Lisp 还包含列表、向量、哈希表等。
然而,Common Lisp 的代码比 Clojure 更严格地遵循列表(函数参数在参数列表中,而不是像 Clojure 那样在向量中)。
因此,您可以更一致地使用函数来操作列表以进行元编程(宏)。
这是否比 Clojure 更“像”是一个哲学问题。
但与 Clojure 相比,您肯定需要一组更小的函数来操作代码。
我记得在 Clojure for Lisp Programmers videos 中重复的一个说法是,早期的 Lisp,特别是 Common Lisp 的一个很大的弱点是太多地与 Lisp 的列表结构结合,特别是 cons
细胞。您可以在链接视频的第 25 分钟标记处找到一次这种说法,但我确信我记得在该系列的其他地方听到过。重要的是,在视频的同一点,我们看到了这张幻灯片,它向我们展示了 Clojure 除了老派的 Lispy 列表之外还有许多其他数据结构:
在 Clojure 中,除了列表之外,某些语言构造所需的额外数据结构中唯一的一个是向量,并且它们位于众所周知的位置,例如围绕函数的参数序列,或者在symbol/expression对一个let
。所有这些都可用于数据文字,但与始终在列表中的函数调用和宏调用相比,在编写 Clojure 宏时,数据文字不太常见。
我不知道在 Clojure 中有什么使得编写宏比在 Common Lisp 中更难,并且有一些不同于 Clojure 的特性可以使它更容易一些,例如 Clojure 反引号的行为默认情况下,表达式将命名空间限定符号,这通常是您要防止 'capturing' 意外命名的原因。
让我们把“同像性”这个词分开。
- homo-,意思是“相同”
- iconic,这里的意思类似于“由 代表”
- -ity,意思是“有这样的东西 属性”
使宏成为可能(或至少更容易)的质量是语言本身使用与您用来表示其他对象相同的数据结构来表示。请注意,这个词不是“单一主义”,也不是“一切都是清单”。由于 Clojure 在运行时使用相同的数据结构来描述其语法,因为它在描述其他值时也使用相同的数据结构,因此没有理由声称它不是谐音。
有趣的是,我可能会说 Clojure 最不具有同形异象的方式之一是它实际上与旧的 lisps 共享的一个特性:它使用符号来表示源代码。在其他 lisp 中,这是很自然的,因为符号用于很多其他事情。不过,Clojure 有关键字,它们更常用于在运行时为数据命名。很少使用符号;我什至可以说它们的主要用途是表示源代码元素!但是 Clojure 用来表示源代码元素的所有其他特性也经常被用来表示其他东西:序列、向量、映射、字符串、数字、关键字、偶尔的集合,以及可能其他一些我没有想到的小众东西现在。
is that everything - yes, everything - is a list
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 添加原始符号 class。
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>
reader:
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
现在我们可以read/print这些对象:
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.
正如前面的回答者所指出的,“同像性”无非就是著名的“code
is data
”:一种编程语言的代码由该语言的 100% 数据结构组成语言,因此可以使用通常用于操作数据结构的语言中的函数轻松构建和操作。
由于 Clojure 数据结构包括列表、向量、映射...(您列出的那些)并且由于 Clojure 代码由列表、向量、映射组成...Clojure 代码是同构的。
Common Lisp 和其他 lisp 也是如此。
Common Lisp 还包含列表、向量、哈希表等。 然而,Common Lisp 的代码比 Clojure 更严格地遵循列表(函数参数在参数列表中,而不是像 Clojure 那样在向量中)。 因此,您可以更一致地使用函数来操作列表以进行元编程(宏)。
这是否比 Clojure 更“像”是一个哲学问题。 但与 Clojure 相比,您肯定需要一组更小的函数来操作代码。