一起来学习 Lisp 编程语言吧!
创始人
2024-03-02 11:35:25
0

许多大型代码库中都有 Lisp 代码的身影,因此,熟悉一下这门语言是一个明智之举。

早在 1958 年,Lisp 就被发明出来了,它是世界上第二古老的计算机编程语言(LCTT 译注:最古老的编程语言是 Fortran,诞生于 1957 年)。它有许多现代的衍生品,包括 Common Lisp、Emacs Lisp(Elisp)、Clojure、Racket、Scheme、Fennel 和 GNU Guile 等。

那些喜欢思考编程语言的设计的人,往往都喜欢 Lisp,因为它的语法和数据有着相同的结构:Lisp 代码实际上是 一个列表的列表 a list of lists ,它的名字其实是 “ 列表处理 LISt Processing ” 的简写。而那些喜欢思考编程语言的美学的人,往往都讨厌 Lisp,因为它经常使用括号来定义范围;事实上,编程界也有一个广为流传的笑话:Lisp 代表的其实是 “大量烦人的多余括号” Lots of Irritating Superfluous Parentheses 。

不管你是喜欢还是讨厌 Lisp 的设计哲学,你都不得不承认,它都是一门有趣的语言,过去如此,现在亦然(这得归功于现代方言 Clojure 和 Guile)。你可能会惊讶于在任何特定行业的大代码库中潜伏着多少 Lisp 代码,因此,现在开始学习 Lisp,至少熟悉一下它,不失为一个好主意。

安装 Lisp

Lisp 有很多不同的实现。比较流行的开源版本有 SBCLGNU LispGNU Common Lisp(GCL)。你可以使用发行版的包管理器安装它们中的任意一个,在本文中,我是用的是 clisp(LCTT 译注:也就是 GNU Lisp,一种 ANSI Common Lisp 的实现)。

以下是在不同的 Linux 发行版中安装 clisp 的步骤。

在 Fedora Linux 上,使用 dnf

$ sudo dnf install clisp

在 Debian 上,使用 apt

$ sudo apt install clisp

在 macOS 上,使用 MacPorts 或者 Homebrew

# 使用 MacPorts
$ sudo port install clisp

# 使用 Homebrew
$ brew install clisp

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

虽然我使用 clisp 命令来运行 Lisp 代码,但是本文中涉及到的大多数语法规则,对任何 Lisp 实现都是适用的。如果你选择使用一个不同的 Lisp 实现,除了用来运行 Lisp 代码的命令会和我不一样外(比如,你可能要用 gclsbcl 而不是 clisp),其它的所有东西都是相同的。

列表处理

Lisp 源代码的基本单元是 “ 表达式 expression ”,它在形式上是一个列表。举个例子,下面就是一个列表,它由一个操作符(+)和两个整数(12)组成:

(+ 1 2)

同时,它也是一个 Lisp 表达式,内容是一个符号(+,会被解析成一个加法函数)和它的两个参数(12)。你可以在 Common Lisp 的交互式环境(即 REPL)中运行该表达式和其它表达式。如果你熟悉 Python 的 IDLE,那么你应该会对 Lisp 的 REPL 感到亲切。(LCTT 译注:REPL 的全称是 “Read-Eval-Print Loop”,意思是 “‘读取-求值-输出’循环”,这个名字很好地描述了它的工作过程。)

要进入到 REPL 中,只需运行 Common Lisp 即可:

$ clisp
[1]>

在 REPL 提示符中,尝试输入一些表达式:

[1]> (+ 1 2)
3
[2]> (- 1 2)
-1
[3]> (- 2 1)
1
[4]> (+ 2 3 4)
9

函数

在了解了 Lisp 表达式的基本结构后,你可以使用函数来做更多有用的事。譬如,print 函数可以接受任意数量的参数,然后把它们都显示在你的终端上,pprint 函数还可以实现格式化打印。还有更多不同的打印函数,不过,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 函数,把某个字符串的所有字符转换成大写,然后再使用 pprint 函数,将它的内容格式化打印到终端上:

[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 才行(LCTT 译注:就 clisp 而言,使用 quit 关键字也可以退出)。

你可以使用 typep 函数对一些对象进行类型检查,它可以测试对象是否为某个特定数据类型。返回值 TNIL 分别代表 TrueFalse

[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
[...]

这是一种保护某些术语(LCTT 译注:类似于字符串转义)的简便方法,正常情况下它是用 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 脚本 时。

下面是一个用 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)

脚本的第一行注释(LCTT 译注:称之为“ 释伴 shebang ”)告诉了你的 POSIX 终端,该使用什么可执行文件来运行这个脚本。

roller 函数使用 defun 函数创建,它在内部使用 random 函数来打印一个伪随机数,这个伪随机数严格小于 num 列表中下标为 0 的元素。在脚本中,这个 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 作为个人脚本的实用语言,还是为了助力你的职业生涯,抑或是仅仅作为一个有趣的实验,你都可以去看看一年一度(LCTT 译注:应该是两年一度)的 Lisp 游戏果酱 Game Jam ,从而收获一些特别有创意的用途(其中的大多数提交都是开源的,因此你可以查看代码以从中学习)。

Lisp 是一门有趣而独特的语言,它有着不断增长的开发者用户群、足够悠久的历史和新兴的方言,因此,它有能力让从事各个行业的程序员都满意。


via: https://opensource.com/article/21/5/learn-lisp

作者:Seth Kenlon 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

相关内容

不同的Common Lis...
不同的Common Lisp实现之间的“不同的“:cl”包处理方式...
2025-01-08 12:31:14
不清楚EmacsLisp函...
下面是一个 Emacs Lisp 函数示例,可用于比较和演示:(d...
2024-12-27 14:01:19
比较列表/制作列表和向量/...
在通用Lisp中,可以使用列表(list)和向量(vector)来...
2024-12-14 21:30:51
编写一个Lisp文件,从键...
以下是一个使用Lisp编写的解决方案示例:(defun read-...
2024-12-07 02:01:49
编写一个Lisp函数tri...
以下是一个使用Lisp编写的函数triangle,用于打印出由奇数...
2024-12-07 02:01:21
编写一个LISP代码,从1...
(defun print-even (lst) (dolis...
2024-12-07 02:00:57

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
2024 开年,LLUG 和你... Hi,Linuxer,2024 新年伊始,不知道你是否已经准备好迎接新的一年~ 2024 年,Lin...
什么是 KDE Connect... 什么是 KDE Connect?它的主要特性是什么?它应该如何安装?本文提供了基本的使用指南。科技日...
Opera 浏览器内置的 VP... 昨天我们报道过 Opera 浏览器内置了 VPN 服务,用户打开它可以防止他们的在线活动被窥视。不过...