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 的改编吗?我记得我哥哥在 1980 年代后期我在课堂上尝试使用 Logo 时告诉我这一点。

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

LISP:Lots of Insipid Stupid Parentheses(大量乏味愚蠢的括号)——这是我们在 1970 年代学习它时(以及当我和一位朋友为我的 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本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.