使用`eval-when`时出现意外行为

时间:2012-08-28 10:00:55

标签: common-lisp slime ccl

以下CL代码片段无法正常运行,因为CCL运行SLIME。如果我 首先使用C-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 *read-default-float-format* '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挤压x86。 SLIME的版本来自Debian unstable,1:20120525-2。 CCL是1.8版本。我用http://svn.clozure.com/publicsvn/openmcl/release/1.8/linuxx86/ccl的上游二进制文件和我创建的二进制包尝试了它 - 请参阅Package ccl at mentors.debian.net。结果在每种情况下都是相同的。

这个问题似乎很可能是特定于SLIME的。如果人们可以评论他们是否看到这种行为会很有帮助。也, 如果在基本命令行模式下运行CCL,SLIME中等效于C-c C-k的是什么? (LOAD filename)还是其他什么?或者,问一个稍微不同的问题,CCL函数是C-c C-k调用的吗?

我注意到在以下代码中调用C-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*)

生成DOUBLE-FLOAT,但即使后面的REPL *read-default-float-format*提供了SINGLE-FLOAT

更新2:正如Rainer所说,看起来编译发生在一个单独的线程中。

根据Threads Dictionary

中的all-processes函数

使用C-c C-k从缓冲区打印all-processes

(#<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)

并在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>)

所以似乎#<PROCESS worker(188) [Active] #x18BF99CE>是正在进行编译的线程。当然,仍然存在这样的问题:为什么这些变量是线程的局部变量,也是为什么SBCL的行为方式不同。

1 个答案:

答案 0 :(得分:3)

我可以看到CCL和一些较旧的(我使用的)SLIME。没有尝试过更新的SLIME。

SBCL或LispWorks不会发生这种情况。

*read-default-float-format*是Common Lisp的I / O变量之一。像WITH-STANDARD-IO-SYNTAX之类的东西将它们绑定到标准值,并在退出时恢复以前的值。所以我怀疑CCL的SLIME代码实际上有这样的绑定。这可以通过设置其他I / O变量来确认,例如*read-base* - 它们也是绑定的。

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

  • Emacs运行SLIME的ELISP代码并与SWANK交谈。
  • SWANK是SLIME的后端,是在CCL中运行的Common Lisp代码。
  • SWANK具有可移植代码和一些特定于CCL的代码。

在Emacs端使用SLIME / ELISP函数SLIME-COMPILE-AND-LOAD-FILE

在SWANK端,调用Common Lisp函数swank:compile-file-for-emacs。 稍后调用SWANK:LOAD-FILE

我看不到I / O变量的绑定位置 - 可能是在CCL网络代码中?

这似乎是答案

如果CCL具有线程局部I / O变量,则编译在另一个线程中发生,并且不会更改REPL线程中的I / O绑定,也不会更改任何其他线程中的I / O绑定。

这是各种CL实现之间的差异。由于CL标准没有指定线程或进程,因此如果特殊绑定是全局的或默认情况下具有线程局部绑定,也不会指定它...

如果你考虑一下,保护线程的I / O变量免受来自其他线程的更改是有意义的......

因此,处理它的正确方法应该是在每个线程中确保正确的I / O变量值有效。


让我扩展一下为什么事情就像它们一样。

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

Lisp有几个变量决定了I / O操作的行为。例如,读取时浮动的格式或读取时的基本整数数。这封信由` read-base 完成。

现在假设上面的磁盘读取线程已将其*read-base*设置为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中,您还可以设置全局静态绑定:(CCL::%SET-SYM-GLOBAL-VALUE sym value)。但是如果你使用这样的功能,你可能做错了什么或者你有充分的理由。

CCL的一些背景知识:http://clozure.com/pipermail/openmcl-devel/2011-June/012882.html

<强>洛雷

  • 不要尝试从其他线程可见的一个线程更改全局绑定。
  • 通过将关键变量绑定到正确的值来保护代码免受其他线程的更改。
  • 编写一个函数,以可控方式将变量值设置为某些值。