如何在 Common Lisp 中使用库?

Whi*_*ist 3 common-lisp asdf quicklisp

我是 Common Lisp 的初学者,我想使用一个库。

我找不到加载/要求/使用模块的一个简单示例。我已经像这样安装了 cl-ppcre :

$ sbcl --non-interactive --eval '(ql:quickload "cl-ppcre")'
To load "cl-ppcre":
  Load 1 ASDF system:
    cl-ppcre
; Loading "cl-ppcre"
..

Run Code Online (Sandbox Code Playgroud)

但我不知道如何实际使用它。我尝试过以下方法和其他十几种方法,但没有一种有效。

$ sbcl --noinform --non-interactive --eval '(progn (require "cl-ppcre") (cl-ppcre:split "\s+" "1 2 3"))'
Unhandled SB-INT:SIMPLE-READER-PACKAGE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                                          {1004DB8073}>:
  Package CL-PPCRE does not exist.

    Stream: #<dynamic-extent STRING-INPUT-STREAM (unavailable) from "(progn (...">

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1004DB8073}>
0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}> #<unused argument> :QUIT T)
1: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)
2: (INVOKE-DEBUGGER #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)
3: (ERROR #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)
Run Code Online (Sandbox Code Playgroud)

那么我怎样才能让它发挥作用呢?

编辑1: 我没有明确指出我使用库的问题与脚本中的问题和终端中的问题一样多。这对我来说是隐含的。这是因为我在 Perl 方面的经验,在 Perl 中,您可以对文件执行的所有操作都可以在命令行执行,包括使用库。

编辑2: 这是我的工作解决方案。事实证明,有两件事是错误的。我的问题需要:

  1. 使用多个--eval

正如Svante和ignis volens所说。

  1. (load "~/.quicklisp/setup.lisp")

我已经在这里解释过:

对 SBCL 中的“ql:quickload”和可执行脚本感到困惑

这是终端解决方案:

sbcl --non-interactive --eval '(load "~/.quicklisp/setup.lisp")' --eval '(require :cl-ppcre)' --eval '(princ (cl-ppcre:split "\\s+" "1  2 3"))'
Run Code Online (Sandbox Code Playgroud)

需要注意的是,在 stderr 上会输出一堆警告,就像这个一样,我不知道为什么会这样。

WARNING: redefining QL-SETUP:QMERGE in DEFUN
Run Code Online (Sandbox Code Playgroud)

这是脚本解决方案:

#!/usr/bin/sbcl --script

(load "~/.quicklisp/setup.lisp")
(require :cl-ppcre)

(princ (cl-ppcre:split "\\s+" "1    2 3"))
(terpri)
Run Code Online (Sandbox Code Playgroud)

ign*_*ens 6

这是人们在 CL 方面遇到的常见问题的一个例子。

CL(和其他 Lisp)的工作方式分为三个阶段(实际上不止三个阶段,但三个阶段就足够了):

  1. 读取字符序列以将其转换为形式;
  2. 该形态会发生各种魔法;
  3. 评估第 2 阶段的结果,并且可能打印结果。

通常,该过程会被迭代以处理文件或输入流等。

重要的是 (1)、(2) 和 (3)按顺序发生:(1) 在 (2) 开始之前完成,并且 (1) 和 (2) 在 (3) 开始之前完成。

这意味着,在 (2) 或 (3) 中发生的任何事情发生之前,(1) 必须是可能的。

所以考虑这种形式:

(progn 
  (ql:quickload "cl-ppcre")
  (cl-ppcre:split "\s+" "1 2 3"))
Run Code Online (Sandbox Code Playgroud)

(这几乎是您尝试评估的形式之一。)

所以问题是:(1)需要什么条件才能发生?嗯,这需要两件事:

  • QL(本质上是一个命名空间:有关 CL 中“包”含义的更多信息,请参阅下文)必须存在才能读取ql:quickload
  • CL-PPCRE包必须存在才能读取cl-ppcre:split

现在您看到了问题:(ql:quickload "cl-ppcre") 创建CL-PPCRE,而这直到 (3) 才会发生。这意味着无法读取此表单。

在绝望中,你可以使用各种英雄技巧来解决这个问题。然而,您实际上并不需要:您可以做其他事情,这(几乎:见下文)有效:

(ql:quickload "cl-ppcre")
(cl-ppcre:split "\s+" "1 2 3")
Run Code Online (Sandbox Code Playgroud)

这(几乎)很好,因为它不是一种形式:它是两种形式。因此,(1)-(3) 适用于第一种形式,然后( 1)-(3) 适用于第二种形式。

因此,答案不是尝试将所有内容捆绑到单一形式中。要么将内容放入文件中(可能是最好的方法),要么如果您确实想将内容作为命令行参数运行,则需要安排表单是独立的,例如具有多个--eval选项。


上面我说过第二个多种形式的版本几乎可以工作。而且它只是几乎有效。其原因是文件编译。假设我有一个文件,其内容是:

(ql:quickload "cl-ppcre")
(cl-ppcre:scan ...)
...
Run Code Online (Sandbox Code Playgroud)

那么,编译器在编译该文件时会做什么呢?它逐个读取文件,但一般来说,它实际上并不执行代码(也有例外):它安排在文件加载时执行该代码。因此编译器不会加载 CL-PPCRE:它会安排生命,以便在加载文件时加载 CL-PPCRE。现在我们有一个相同问题的版本:编译器无法读取第二种形式,因为包CL-PPCRE尚不存在。

好吧,有一个解决方案:您可以告诉编译器它实际上必须在编译时执行一些代码:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload "cl-ppcre"))
(cl-ppcre:scan ...)
...
Run Code Online (Sandbox Code Playgroud)

现在,由于eval-when形式的原因,编译器知道它必须调用ql:quickload. 它会这样做,因此CL-PPCRE包将在编译时定义,一切都会好起来的。


关于 CL 中的包的注释

不幸的是,CL 中术语“包”的含义与许多其他语言中的含义不同:这是由于历史原因,现在无法更改。

在常见用法中,包是“您可以安装的一些代码块,并且可能依赖于其他包(代码块),所有这些都可能由某种包管理器来管理。您可以将 Python 作为软件包安装在 Ubuntu 机器上,也可以使用conda软件包管理器来管理科学 Python 软件包(包括 Python 本身)。

在 CL 中,包本质上是一个命名空间BAR如果我输入“foo:bar”,那么这指的是名称或昵称之一的包中名为可用的符号FOO。此外,这是一个“外部”符号,这意味着它旨在以某种方式公开。包是 CL 中的真实对象,可以通过程序进行推理。始终存在当前包的概念,并且该包通过直接包含某些名称以及具有其他包的搜索(“使用”)列表来定义可用的名称,而不需要包前缀。CL 中的包有很多内容:远远超出我在这里所能提及的范围。

通常被称为包的东西在 CL 中可能最好被称为“库”:它们是可以安装的代码块,并且它们本身可能具有依赖项。或者,它们通常被称为“系统”,因为它们通常使用“系统定义”工具来定义。“图书馆”可能是更好的术语:“系统”再次成为历史上的怪事,现在无法真正改变。