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 游戏 Jam 上看到一些特别有创意的用途(大多数提交都是开源的,因此您可以查看代码以从您玩的游戏中学习)。
Lisp 是一种有趣且独特的语言,拥有不断增长的开发者基础,并且有足够多的历史和新兴方言来让所有学科的程序员都感到满意。

12 条评论