3.3 语法树和 LISP 语言

    前文讲到,FORTH 语言不需要使用括号或者优先次序就可以表达计算顺序。现实中,有的语言总是需要用括号标示完整的意思单元,比如 1958 年诞生的 LISP 语言。

    计算流

    LISP 中 1 加 2 用代码表达如下:9

    9希望实际执行一下 LISP 语言的读者可以到笔者制作的网页上尝试一下:http://nhiro.org/learn_language/LISP-on-browser.html
    (+ 1 2)

    首先是一个括号,接着是加号命令,用空格分隔后,跟上进行相加运算的对象。

    如何表达计算顺序

    我们来看如何用代码表达 1 与 2 相加的结果与 3 相乘。把[相乘、(什么)、3]中的(什么)换成(相加、1、2),我们得到:

    (* (+ 1 2) 3)

    这个过程如图 3.2 所示。❶为(1 与 2 相加),在 LISP 语言中,代码表示为(+ 1 2)。图 3.2 把命令放在上方,把(什么)这部分放在下方。❷为[(什么)与 3 相乘]。(什么)这部分用?表示。❸为把(1 与 2 相加)代入(什么)这部分的结果。图 3.2 这样的结构称为语法树。

    空标题文档 - 图1

    图 3.2 语法树的结构

    我们把这个语法树和 FORTH 语言和 LISP 语言的代码放在一起比较一下。

    空标题文档 - 图2

    图 3.3 FORTH 和 LISP 的语法树是相同的

    可以看出,对于这两种语法简单的语言,它们只是在这个语法树上按不同的规则遍历而已。两者的代码看起来差别很大,但实际上所用的树结构是相同的。

    现在仍然使用的语法树

    当今的语言仍然在使用语法树。我们用 Python 语言实际执行一下。通过 Python 语言自带的库文件 ast,我们可以查看特定的代码被转换成怎样的语法树 10。

    10ast 是 abstract syntax tree 的简写,意为抽象语法树。在语法复杂的语言中,语法树是包含很多细节的语法结构表达形式。把这种形式以更简洁的形式表达出来就是抽象语法树。
    Python
    >>> import ast
    >>> ast.dump(ast.parse("1 + 2"))
    Module(
    body=[Expr(
    value=BinOp(
    left=Num(n=1),
    op=Add(),
    right=Num(n=2))
    )
    ]
    )

    >>> ast.dump(ast.parse("(1 + 2) * 3"))
    Module(
    body=[Expr(
    value=BinOp(
    left=BinOp(
    left=Num(n=1),
    op=Add(),
    right=Num(n=2)
    ),
    op=Mult(),
    right=Num(n=3))
    )
    ]
    )

    我们将其和 LISP 比较一下。Python 语言这边看起来更为错综复杂,但我们还是能察觉到两者有着共通的结构。图 3.4 中 BinOp op =Mult( ) 表示乘法运算,与 LISP 的 * 相对应;BinOp op =Add( ) 表示加法运算,与 LISP 的 + 相对应。Num n=1 即为数值 1,这与 LISP 的 1 相对应。

    空标题文档 - 图3

    图 3.4 Python 语言与 LISP 语言的语法树比较

    专栏
    要确认理解是否正确,首先得表达出来
    假设你正在学习一些知识,并自我感觉已经理解了。那么,你到底是真正理解了呢,还是感觉自己已经理解了呢?这个仅凭自己苦思冥想是不行的。为了验证理解正确与否,需要表达出来。只能基于自己的理解说出自己的观点,然后让第三方来判断和检验。比如学习英语,就要在别人面前使用自己学到的英语,同时观察别人的反应。不这样做的话,就无法知道自己是否真正掌握了英语。
    程序员一直受益于这一点。如果是写文章,写出来的东西即使有错误也可能没人指出来,或者根本没有人看你的东西。但是写程序不一样,语言处理器会事无巨细地做错误检查并指出。这和与人打交道不同,只要你方便,它总是有足够的时间和耐心陪你一起。
    一旦出现程序错误,很多人可能会惊慌失措。其实那只是语言处理器在仔细阅读了你的程序后,告诉你它哪里不明白而已。只有理解了这一点,才能和语言处理器打交道。

    LISP 语言语法简单,代码与语法树容易理解并且对应比较直观。此外,它还具有宏这样的语法树替换机制。这两个特点催生了程序设计语言进化路上发生的结构化编程等一系列现象。详细内容我们将在第 4 章讲解。