Common lisp 带有“eval when”的意外行为`

Common lisp 带有“eval when”的意外行为`,common-lisp,slime,ccl,Common Lisp,Slime,Ccl,下面的CL代码片段在CCL运行SLIME时不能像我预期的那样工作。如果我 首先使用C-C-k编译并加载文件,然后运行 (rdirichlet #(1.0 2.0 3.0) 1.0) 在SLIME/CCL REPL中,我得到了错误 value 1.0 is not of the expected type DOUBLE-FLOAT. [Condition of type TYPE-ERROR] 它与SBCL一起工作。我希望(setf*读取默认浮点格式*'double float))允许我

下面的CL代码片段在CCL运行SLIME时不能像我预期的那样工作。如果我 首先使用
C-C-k
编译并加载文件,然后运行

(rdirichlet #(1.0 2.0 3.0) 1.0)
在SLIME/CCL REPL中,我得到了错误

value 1.0 is not of the expected type DOUBLE-FLOAT.
   [Condition of type TYPE-ERROR]
它与SBCL一起工作。我希望
(setf*读取默认浮点格式*'double float))
允许我使用
1.0
之类的值。如果我在REPL处使用
load
将此文件加载到CCL中,它就会工作。我错过了什么

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :asdf) (require :cl-rmath) (setf *read-default-float-format* 'double-float))

(defun rdirichlet (alpha rownum)
  ;;(declare (fixnum rownum))
  (let* ((alphalen (length alpha))
    (dirichlet (make-array alphalen :element-type '(double-float 0.0 *) :adjustable nil :fill-pointer nil :displaced-to nil)))
    (dotimes (i alphalen)
      (setf (elt dirichlet i) (cl-rmath::rgamma (elt alpha i) rownum)))
    ;; Divide dirichlet vector by its sum
    (map 'vector #'(lambda (x) (/ x (reduce #'+ dirichlet))) dirichlet)))
更新:我忘了提到我的平台和版本。我使用的是Debian squeeze x86。SLIME的版本来自Debian不稳定,
1:20120525-2
。 CCL是1.8版本。我用来自的上游二进制文件和我创建的二进制软件包进行了尝试-请参阅。每种情况的结果都是一样的

这一问题似乎有可能与粘液有关。如果人们能够评论他们是否看到这种行为,这将是很有帮助的。也, 如果在基本命令行模式下运行CCL,SLIME中的
C-C-k
等效于什么<代码>(加载文件名),还是其他什么?或者,问一个稍微不同的问题,
C-C-k
调用什么CCL函数

我注意到对以下代码调用
C-C-k

(eval-when (:compile-toplevel :load-toplevel :execute)
      (require :asdf) (require :cl-rmath) (setf *read-default-float-format* 'double-float))

(print *read-default-float-format*)
产生
双浮点数
,尽管在REPL处读取默认浮点数格式*后立即给出
单浮点数

更新2:正如Rainer所说,编译似乎发生在一个单独的线程中

根据功能
中的所有进程

使用C-C-k从缓冲区打印所有进程

(#<PROCESS worker(188) [Active] #x18BF99CE> #<PROCESS repl-thread(12) [Semaphore timed wait] #x187A186E> #<PROCESS auto-flush-thread(11) [Sleep] #x187A1C9E> #<PROCESS swank-indentation-cache-thread(6) [Semaphore timed wait] #x186C128E> #<PROCESS reader-thread(5) [Active] #x186C164E> #<PROCESS control-thread(4) [Semaphore timed wait] #x186BE3BE> #<PROCESS Swank Sentinel(2) [Semaphore timed wait] #x186BD0D6> #<TTY-LISTENER listener(1) [Active] #x183577B6> #<PROCESS Initial(0) [Sleep] #x1805FCCE>)
CL-USER> (all-processes)
(##########)
CL-USER>(所有流程)
在REPL给出的

(#<PROCESS repl-thread(12) [Active] #x187A186E> #<PROCESS auto-flush-thread(11) [Sleep] #x187A1C9E> #<PROCESS swank-indentation-cache-thread(6) [Semaphore timed wait] #x186C128E> #<PROCESS reader-thread(5) [Active] #x186C164E> #<PROCESS control-thread(4) [Semaphore timed wait] #x186BE3BE> #<PROCESS Swank Sentinel(2) [Semaphore timed wait] #x186BD0D6> #<TTY-LISTENER listener(1) [Active] #x183577B6> #<PROCESS Initial(0) [Sleep] #x1805FCCE>)
(#########)

因此,似乎执行编译的线程是
#
。当然,仍然存在这样一个问题:为什么这些变量是线程的局部变量,以及为什么SBCL的行为方式不同。

我可以看到CCL和一些旧的(我使用的)SLIME也有这样的问题。还没有用新的粘液试过

SBCL或LispWorks不会发生这种情况

*读取默认浮点格式*
是Common Lisp的I/O变量之一。类似于
WITH-STANDARD-IO-SYNTAX
的东西将它们绑定到标准值,并在退出时恢复以前的值。所以我怀疑CCL的SLIME代码有这样的约束力。这可以通过设置其他I/O变量(如
*read base*
)来确认,它们也被绑定

CCL、Emacs和SLIME有一些代码层,这使得调试有点复杂

  • Emacs运行SLIME的ELISP代码并与SWANK对话
  • SWANK是SLIME的后端,是在CCL中运行的常见Lisp代码
  • SWANK有可移植代码和一些特定于CCL的代码
在Emacs端,使用SLIME/ELISP函数
SLIME-COMPILE-AND-LOAD-FILE

在SWANK端,调用公共Lisp函数
SWANK:compilefile for emacs
。 稍后调用
SWANK:LOAD-FILE

我不知道I/O变量绑定在哪里——可能是在CCL网络代码中

这似乎是答案:

如果CCL具有线程本地I/O变量,那么编译将在另一个线程中进行,并且不会更改REPL线程中的I/O绑定,也不会更改任何其他线程中的I/O绑定

这是不同CL实现之间的差异。由于CL标准未指定线程或进程,因此如果特殊绑定是全局绑定或默认情况下具有线程本地绑定,也未指定该标准

仔细想想,保护线程的I/O变量不受其他线程更改的影响是有意义的

因此,正确的处理方法应该是确保在每个线程中独立地使用正确的I/O变量值


让我来解释一下为什么事情是这样的

通常,公共Lisp实现可以运行多个线程。一些实现还允许并发线程在不同的内核上同时运行。这些线程可以做非常不同的事情:一个可以运行REPL,另一个可以响应HTTP请求,一个可以从磁盘加载数据,另一个可以读取电子邮件的内容。在本例中,Lisp在一个Lisp系统中运行多个不同的任务

Lisp有几个变量,它们决定I/O操作的行为。例如,读取时浮动的格式或读取时的基本整数。这封信是由readbase写的

现在想象一下,出于某种目的,上面的磁盘读取线程已将其
*readbase*
设置为16。现在您将另一个线程中的全局值更改为8,然后突然所有其他线程都有了基数8。结果:磁盘读取线程将突然看到
*read base*
8而不是16,并且工作方式不同

因此,以某种方式预防这种情况是有意义的。最简单的是,在每个线程中,运行的代码都有自己的I/O值绑定,然后更改
*read base*
不会对其他线程产生影响。这些绑定通常由
LET
或函数调用引入。通常,代码将负责绑定变量

另一种防止这种情况的方法是为每个线程提供一些初始绑定,例如,这些绑定应该包括I/O绑定。CCL就是这么做的。例如,LispWorks也会这样做。但不适用于I/O变量

现在,每个Lisp都可能为您提供一种不可移植的方法来更改线程本地顶部绑定(CCL也有这种方法-例如
(setf CCL:symbol value in process)
)。但这并不意味着它可能会改变REPL中有效的绑定。因为REPL本身是一段Lisp代码,在线程中运行,它可能已经设置了自己的绑定

在CCL中,您还可以