在 2021 年学习 Lisp 编程语言

许多大型代码库中潜藏着 Lisp 代码,因此熟悉这门语言是很明智的。
81 位读者喜欢这篇文章。
Woman sitting in front of her laptop

kris krüg

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 有许多实现。流行的开源版本包括 SBCLGNU LispGNU Common Lisp (GCL)。你可以使用发行版的包管理器安装其中任何一个,但在本文中我使用 clisp

在 Fedora Linux 上

$ sudo dnf install clisp

在 Debian 上

$ sudo apt install clisp

对于 macOS,你可以使用 MacPortsHomebrew

$ sudo port install clisp

对于 Windows,你可以使用 Cygwin 上的clisp 或从 gnu.org/software/gcl 下载 GCL 二进制文件。

即使我使用的是 clisp 命令,本文中的大多数原则也适用于任何 Lisp。如果你选择使用不同的 Lisp 实现,则运行 Lisp 代码的命令与我在本文中使用的命令不同(例如,gclsbcl 而不是 clisp),但其他一切都相同。

列表处理

Lisp 源代码的基本单元是表达式,它被写成列表。例如,这是一个运算符 (+) 和两个整数(12)的列表

(+ 1 2)

它也是一个 Lisp 表达式,使用一个符号 (+),该符号求值为一个函数(加法)和两个参数(12)。你可以在名为 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 会话中,foobar 都设置为带引号的数字,因此 Lisp 将它们解释为字符串。数学运算符不能用于字符串,因此 REPL 进入调试器模式。要退出调试器,请按键盘上的 Ctrl+D

你可以使用 typep 函数对对象进行一些内省,该函数测试特定的数据类型。标记 TNIL 分别代表

[4]> (typep foo 'string)
NIL
[5]> (typep foo 'integer)
T

stringinteger 之前的单引号 (') 阻止 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 是一种有趣且独特的语言,拥有不断增长的开发者基础,以及足够多的历史和新兴方言,可以满足所有学科程序员的需求。

接下来阅读什么
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,通常同时进行。

12 条评论

我总是从你那里学到新东西。你知道 Seymour Papert 等人开发的 Logo 计算机语言是 Lisp 的一种改编吗?我记得 20 世纪 80 年代后期我在课堂上尝试使用 Logo 时,我的兄弟告诉过我这件事。

非常喜欢这篇文章,我以前只听说过 Lisp,但看到这篇文章让我想要尝试一下。谢谢。

LISP:大量乏味愚蠢的括号 - 这是我们在 20 世纪 70 年代学习它时(以及当我和一位朋友为我的 Altair 编写 LISP 解释器时)对它的称呼,现在仍然如此。从学术角度来看,它很有趣。除此之外,它 a) 难以阅读 b) 难以调试 c) 使用一种除了直观之外的任何符号,因此 LISP 代码往往难以编写并且非常难以维护,更不用说安全性了。FORTH 在当时也很有趣,并且在资源非常有限的机器上确实很有用。今天,它是一种好奇的事物,也应该是。LISP 也是一种好奇的事物,并且属于学术界,几乎没有其他地方,Emacs 也不能幸免 - 在 IBM 704 上首次构思 - 那是真空管,伙计们 - 以及 IBM 7090。

如果你仔细听,你会听到 Racket、Guile、Fennel 和 Lisp 用户在整个星系中同时呼喊的声音。

回复 作者 cube1

我开始起荨麻疹了。有人起荨麻疹吗?伙计们……Lisp 让我起荨麻疹了!

“GCL 及其 clisp 命令”

如果我 '$ sudo dnf install gcl' 然后 'clisp' 它会说:“command not found”

但是 '$ gcl' 可以工作。
这意味着 'shebang' 应该是 '#!/usr/bin/gcl'?

是的,我急于演示你有多少 Lisp 选择,恐怕我把事情搞糊涂了。
我已经为未来的读者修改了这篇文章。

安装 clisp,文章中的所有内容都可以正常工作,无需调整。

sudo dnf install clisp

回复 作者 openharry

./dice.lisp 不起作用。
它将启动 gcl,我看到一个提示符。
Fedora 34 live CD

我急于演示你有多少 Lisp 选择,恐怕我把事情搞糊涂了。
我已经为未来的读者修改了这篇文章。

安装 clisp,文章中的所有内容都可以正常工作,无需调整。

sudo dnf install clisp

回复 作者 openharry

你的错误是编写 '$ sudo dnf install gcl',而应该安装 'clisp'。然后你的脚本就可以工作了。为了更好地模仿真正的骰子,它应该有
(pprint (+ 1 (random (parse-integer (nth 0 num)))))
然后 './dice.lisp 6' 将给出 {1..6} 范围内的结果

你作弊了 :-) 我本来打算将其作为家庭作业留给读者,让他们弄清楚如何调整范围。不过,有人会感谢你的提示!

回复 作者 openharry

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.