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]>
变量
你可以在 Lisp 中使用 setf
创建变量
[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)
T
string
和 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 条评论