Lisp 发明于 1958 年,是第二古老的计算机编程语言。它衍生出了几个现代分支,包括 Common Lisp、Emacs Lisp (Elisp)、Clojure、Racket、Scheme、Fennel 和 GNU Guile。
喜欢思考编程语言设计的人通常喜欢 Lisp,因为它语法和数据共享相同的结构:Lisp 代码本质上是列表的列表,其名称是LISt Processing(列表处理)的缩写。喜欢思考编程语言美学的人通常讨厌 Lisp,因为它频繁使用括号来划定作用域;事实上,一个常见的笑话是 Lisp 代表Lots of Irritating Superfluous Parentheses(大量令人 раздражающих 多余的括号)。
无论你喜欢还是讨厌它的设计理念,Lisp 都是对过去的有趣一瞥,并且由于 Clojure 和 Guile,也瞥见了未来。你可能会惊讶于任何给定行业的庞大代码库中潜藏着多少 Lisp 代码,因此至少对这门语言有一定的了解是个好主意。
安装 Lisp
Lisp 有许多实现。流行的开源版本包括 SBCL、GNU Lisp 和 GNU Common Lisp (GCL)。你可以使用发行版的包管理器安装其中任何一个,但在本文中我使用 clisp。
在 Fedora Linux 上
$ sudo dnf install clisp在 Debian 上
$ sudo apt install clisp对于 macOS,你可以使用 MacPorts 或 Homebrew
$ sudo port install clisp对于 Windows,你可以使用 Cygwin 上的clisp 或从 gnu.org/software/gcl 下载 GCL 二进制文件。
即使我使用的是 clisp 命令,本文中的大多数原则也适用于任何 Lisp。如果你选择使用不同的 Lisp 实现,则运行 Lisp 代码的命令与我在本文中使用的命令不同(例如,gcl 或 sbcl 而不是 clisp),但其他一切都相同。
列表处理
Lisp 源代码的基本单元是表达式,它被写成列表。例如,这是一个运算符 (+) 和两个整数(1 和 2)的列表
(+ 1 2)它也是一个 Lisp 表达式,使用一个符号 (+),该符号求值为一个函数(加法)和两个参数(1 和 2)。你可以在名为 REPL(读取-求值-打印循环)的交互式 Common Lisp 环境中运行此表达式和其他表达式。如果你熟悉 Python 的 IDLE,那么 Lisp 的 REPL 应该会让你感到有些熟悉。
要启动 REPL,请启动 Common Lisp
$ clisp
[1]> 在 REPL 提示符下,键入几个表达式
[1]> (+ 1 2)
3
[2]> (- 1 2)
-1
[3]> (- 2 1)
1
[4]> (+ 2 3 4)
9函数
现在你已经了解了 Lisp 表达式的基本结构,你可以以有用的方式利用 Lisp 函数。print 函数接受你提供的任何参数,并在你的终端上显示它,而 pprint 函数会“漂亮地”打印它。print 函数还有其他变体,但 pprint 在 REPL 中很好用
[1]> (pprint "hello world")
"hello world"
[2]>你可以使用 defun 创建自己的函数。defun 函数需要你函数的名称和你希望函数接受的任何参数
[1]> (defun myprinter (s) (pprint s))
MYPRINTER
[2]> (myprinter "hello world")
"hello world"
[3]>变量
你可以使用 setf 在 Lisp 中创建变量
[1]> (setf foo "hello world")
"hello world"
[2]> (pprint foo)
"hello world"
[3]>你可以在表达式中嵌套表达式,形成一种管道。例如,你可以在调用 string-upcase 函数将其字符转换为大写后,漂亮地打印变量的内容
[3]> (pprint (string-upcase foo))
"HELLO WORLD"
[4]> Lisp 是动态类型的,这意味着你在设置变量时不必声明变量类型。Lisp 默认将整数视为整数
[1]> (setf foo 2)
[2]> (setf bar 3)
[3]> (+ foo bar)
5如果你打算将整数解释为字符串,你可以引用它
[4]> (setf foo "2")
"2"
[5]> (setf bar "3")
"3"
[6]> (+ foo bar)
*** - +: "2" is not a number
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead.
ABORT :R2 Abort main loop
Break 1 [7]>在此示例 REPL 会话中,foo 和 bar 都设置为带引号的数字,因此 Lisp 将它们解释为字符串。数学运算符不能用于字符串,因此 REPL 进入调试器模式。要退出调试器,请按键盘上的 Ctrl+D。
你可以使用 typep 函数对对象进行一些内省,该函数测试特定的数据类型。标记 T 和 NIL 分别代表真和假。
[4]> (typep foo 'string)
NIL
[5]> (typep foo 'integer)
Tstring 和 integer 之前的单引号 (') 阻止 Lisp(错误地)将这些关键字评估为变量
[6]> (typep foo string)
*** - SYSTEM::READ-EVAL-PRINT: variable STRING has no value
[...]这是一种保护术语的简写方式,通常使用 quote 函数完成
[7]> (typep foo (quote string))
NIL
[5]> (typep foo (quote integer))
T列表
毫不奇怪,你也可以在 Lisp 中创建列表
[1]> (setf foo (list "hello" "world"))
("hello" "world")可以使用 nth 函数索引列表
[2]> (nth 0 foo)
"hello"
[3]> (pprint (string-capitalize (nth 1 foo)))
"World"退出 REPL
要结束 REPL 会话,请按键盘上的 Ctrl+D,或在 Lisp 中使用 quit 关键字
[99]> (quit)
$脚本编写
Lisp 可以被编译或用作解释型脚本语言。当你刚开始时,后者可能是最简单的选择,特别是如果你已经熟悉 Python 或 shell 脚本。
这是一个用 GNU Common Lisp 编写的简单掷骰子脚本
#!/usr/bin/clisp
(defun roller (num)
(pprint (random (parse-integer (nth 0 num))))
)
(setf userput *args*)
(setf *random-state* (make-random-state t))
(roller userput)第一行告诉你的 POSIX 终端要使用哪个可执行文件来运行脚本。
使用 defun 创建的 roller 函数使用 random 函数打印一个伪随机数,该数字最大为 num 列表的第零项,但不包括第零项。num 列表尚未在脚本中创建,但该函数直到被调用才会被执行。
下一行将启动时提供给脚本的任何参数分配给名为 userput 的变量。userput 变量是一个列表,它在传递给 roller 函数后会变成 num。
脚本的倒数第二行启动一个随机种子。这为 Lisp 提供了足够的熵来生成一个大致随机的数字。
最后一行调用自定义的 roller 函数,并将 userput 列表作为其唯一参数提供。
将文件另存为 dice.lisp 并标记为可执行
$ chmod +x dice.lisp最后,尝试运行它,为其提供一个最大数字,从中选择其随机数
$ ./dice.lisp 21
13
$ ./dice.lisp 21
7
$ ./dice.lisp 21
20还不错!
你可能会注意到,你的模拟骰子有一个潜在值 0,并且永远不会达到你作为参数提供给它的最大数字。换句话说,这个脚本永远不会在 20 面骰子上掷出 20(除非你将 0 算作 20)。对此有一个简单的修复方法,你只需要使用从本文中学到的知识即可做到这一点。你能修复这个错误吗?
学习 Lisp
无论你是否可以想象将 Lisp 用作个人脚本的实用语言、提升你的职业生涯,还是仅仅作为一个有趣的实验,你都可以在年度 Lisp 游戏大赛 中看到一些特别有创意的用途(大多数提交都是开源的,因此你可以查看代码,从你玩的游戏中学习)。
Lisp 是一种有趣且独特的语言,拥有不断增长的开发者基础,以及足够多的历史和新兴方言,可以满足所有学科程序员的需求。

12 条评论