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)
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 条评论