已经有一个维基百科条目了(https://en.wikipedia.org/wiki/S-expression),但是如果你不想深入了解,那并没有什么帮助。
什么是S-Expression?我可以用S-Expression表达什么?Lisp通常使用S-Expression的目的是什么?S-Expression只与Lisp开发者相关吗?
(foo . bar)
这是一个cons单元,其car是原子foo
,cdr是原子bar
。我们还可以嵌套cons单元。
((foo . bar) . (baz . potato))
(a . (b . (c . (d . NIL))))
最外一层 cons cell 的 car 是列表中的第一个元素,即 a
。cdr 存储了列表的其余部分。cdr 的 car 是第二个元素 b
,以此类推。(这就是为什么我说不要将 cdr 称为“第二”个元素,因为“第二”通常用来表示“cdr 的 car”)
事实上,我们经常这样做,以至于 Lisp 中还有另一种符号惯例。如果 cdr 是另一个 cons cell,则我们简单地删除 .
和括号并理解其含义。因此,通常情况下,对于任何 S 表达式 a
、b
和 c
,以下两个表达式等价。
(a . (b . c)) === (a b . c)
我并未更改定义,仍然只有两种有效的S表达式:原子和cons cell。我只是发明了一种更紧凑的写法。
同样地,由于我们将经常使用NIL
来结束列表,因此我们可以简单地删除它。如果我们在cons单元格的cdr中有一个NIL
,那么根据惯例,我们会去掉 .
和NIL
。对于任何S表达式a
,以下写法是等效的。
(a . NIL) === (a)
再次说明,我只是在创造一种新的紧凑写法,而不是改变定义。
最后,作为一种符号上的便利,我们可能有时会将原子NIL
写成一对空括号,因为它应该看起来像一个空列表。
NIL === ()
现在,回顾一下我们之前列出的清单
(a . (b . (c . (d . NIL))))
我们可以使用这些规则来简化它
(a . (b . (c . (d . NIL))))
(a b . (c . (d . NIL)))
(a b c . (d . NIL))
(a b c d . NIL)
(a b c d)
现在这看起来非常像Lisp语法。这就是S表达式的优美之处。你所编写的Lisp代码只是一堆S表达式。例如,考虑下面的Lisp代码:
(mapcar (lambda (x) (+ x 1)) my-list)
以下是普通的Lisp代码,这种代码在任何日常程序中都能看到。在Common Lisp中,它将1添加到my-list
的每个元素上。但美妙之处在于它只是一个大的S表达式。如果我们移除所有的语法糖,就得到了:
(mapcar . ((lambda . ((x . NIL) . ((+ . (x . (1 . NIL))) . NIL))) . (my-list . NIL)))
至少在美学上并不好看,但现在更容易看出这实际上只是一堆细胞与原子结尾的列表。你整个Lisp语法树就是那样:一个充满代码的二叉树。而且你可以像操作数据结构一样操作它。你可以编写接受该树作为数据结构的宏,并对其进行任何操作。你的Lisp程序的抽象语法树不是语言内部的不透明构造;它只是一棵树:一种非常简单的数据结构,在日常编程中已经使用了。你在Lisp程序中用来存储数据的相同列表和其他结构也用于存储代码。
现代Lisp方言通过新的约定和在某些情况下引入新的数据类型来扩展此功能。例如,Common Lisp添加了一个数组类型,因此#(1 2 3 4 5)
是一个由五个元素组成的数组。它不是链表(由于在实践中,链表随机访问速度较慢),它完全是另一种东西。同样,Lisp方言在我们已经讨论过的NIL
之上添加了新的约定。在大多数Lisp方言中,撇号或单引号用于表示对quote
特殊形式的调用。
'x === (quote x) (quote . (x . NIL))
x
,不同的方言会向原始的McCarthy定义添加不同的特性,但核心概念是:我们需要什么绝对最少的定义来舒适地存储Lisp程序的代码和数据。0
、000
或#x0
。文本(0 . 1)
是表示cons单元对象的S表达式,其字段是整数零和一。在Common Lisp中,在默认读取表下,标记Foo
、fOO
、FOO
、|FOO|
和foo
都是表示相同符号的S表达式。它们是不同的读取语法,通过它们表示相同对象的语义等效。S表达式或多或少是指具有历史联系和更广泛解释的含义,超出了任何一个Lisp方言。例如,Ron Rivest,也许最知名的RSA加密系统作者之一,写了一篇互联网草案,描述了一种用于数据交换的S表达式形式。
(this (s expression) (could (be represented)) as (this tree))
[..........]
/| | | |
/ . | as .
/ / \ | / \
/ s | . this |
this | |\ tree
| | \
expression | \
could .
|\
be represented
nil
和cons
递归构建(所有Lisp方言的确切细节都有很大差异)。Sexp
也使用了S表达式。实际上,在可以使用S表达式的地方,更常用的是具有更强类型的其他表示数据的方式,例如JSON。((map string single-float) (a 1e12) (b 0.5))
是一种有效的表示从字符串到浮点数值的哈希映射的方式。 - coredump@2022-10-24T16:24:12.913732Z
语法来读取日期(JSON没有这个功能)。所以我认为我的观点是,S-expr被相当宽泛地用来谈论许多事情;至少有一次尝试规范化格式:http://people.csail.mit.edu/rivest/Sexp.txt,这有点像JSON RFC。 - coredump6
,字符串6表示为"6"
。 - Rainer Joswigs表达式是符号表达式的缩写。
基本上,它们是符号和符号嵌套列表。
一个符号由字母数字字符构成。
符号和符号嵌套列表的示例:
foo
berlin
fruit
de32211
(apple peach)
(fruit (seller fruit-co))
((apple one) (peach two))
这些列表由cons单元组成,表示为(one . two),而空列表则表示为nil。
例如:
(a . (b . nil)) -> (a b)
((a . nil) (b . nil)) -> ((a) (b))
编程语言Lisp(缩写为List Processor)被设计用于处理这些列表。Lisp包含各种基本操作来处理嵌套列表。s表达式的元素也可以是数字、字符、字符串、数组和其他数据结构。
符号表达式与JSON和XML具有相同的目的:它们编码数据。
Lisp中的符号表达式也用于编码Lisp程序本身。
示例:
((lambda (a b)
(+ a (* 2 b)))
10
20)
(a . (b . nil)) -> (a b)
的意思,或者也许你移除这个例子,我都会接受这个答案。 - undefined