为什么 SBCL(或一般的 Common lisp?)不喜欢我的循环?
Why does SBCL (or Common lisp in general?) dislike my loop?
递归 find-dir-upwards 按预期工作,但 find-dir-upwards-loop 拒绝编译,在 SBCL(Win64、Portacle 1.4、SBCL 2.0.0)下抱怨类型不匹配(即使添加了类型注释) ).
我做错了什么?
(defun find-dir-upwards-loop (dir marker-file)
(loop for prev = nil then curr
for curr = dir then (uiop:pathname-parent-directory-pathname curr)
until (equal curr prev)
with f = (merge-pathnames marker-file curr)
when (uiop:file-exists-p f) return curr))
(defun find-dir-upwards (dir marker-file)
(let ((f (merge-pathnames marker-file dir)))
(if (uiop:file-exists-p f)
dir
(let ((parent (uiop:pathname-parent-directory-pathname dir)))
(unless (equal dir parent)
(find-dir-upwards parent marker-file))))))
简化代码
经过一番思考,我意识到您可以将原始功能简化为:
(defun find-dir-upwards (dir marker-file)
(let ((parent (uiop:pathname-parent-directory-pathname dir)))
(unless (equal dir parent)
(if (probe-file (merge-pathnames marker-file dir))
dir
(find-dir-upwards parent marker-file)))))
其对应的循环函数为:
(defun find-dir-upwards (dir marker-file)
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (probe-file (merge-pathnames marker-file dir))
(return dir)
(setf dir parent))))
正如@Rezo 在 中指出的那样,probe-file
可以在不同的 Common Lisp 实现之间移植,而 uiop:file-exists-p
则不能。
原回答
我会以不同的方式定义循环。
使用 (let ((dir dir)) (loop ... (setf dir (<some updater function>))))
习惯用法来获取一些引用变量,这些变量在循环中每 运行 更新一次 - 直到满足条件。
(defun find-dir-upwards (dir marker-file)
(let ((f (merge-pathnames marker-file dir))
(dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (uiop:file-exists-p f) ;; if marker-file in dir
(return dir) ;; then arrived at destination
(progn
(setf dir parent) ;; otherwise climb up one dir-level
(setf f (merge-pathnames marker-file dir)))))))
然后进一步分解为:
(defun marker-file-in-dir-p (dir marker-file)
(uiop:file-exists-p (merge-pathnames marker-file dir)))
(defun find-dir-upwards (dir marker-file)
(let ((dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir) ;; parent from dir
unless (equal dir parent)
do (if (marker-file-in-dir-p dir marker-file)
(return dir)
(setf dir parent)))))
将 'FOR' 和 'WITH' 的步骤形式结合起来会产生这个问题。它似乎试图在 'for' 创建 x 之前初始化 'with',但并非总是如此。我还没有解决它,但我以前遇到过这个问题。例如:
(loop for x = 0 then (+ x 1)
with y = (+ x 1)
when (> x 10) return (list x y))
Yields:
Value of X in (+ X 1) is NIL, not a NUMBER.
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "new-repl-thread" RUNNING {10044CC083}>)
Backtrace:
0: (SB-C::%COMPILE-TIME-TYPE-ERROR (NIL) NUMBER #<unused argument> (X) "(+ X 1)" NIL)
1: ((LAMBDA ()))
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LOOP FOR X = 0 THEN ...) #<NULL-LEXENV>)
3: (EVAL (LOOP FOR X = 0 THEN ...))
--more--
但是删除步骤形式或 with 似乎工作正常:
CL-USER> (loop for x from 0 to 10
with y = (+ x 1)
collect (list x y))
((0 1) (1 1) (2 1) (3 1) (4 1) (5 1) (6 1) (7 1) (8 1) (9 1) (10 1))
CL-USER> (loop for x = 0 then (+ x 1)
when (> x 10) return x)
11
您不能(可移植地)在 until
之后放置 with
。看Hyperspec中的语法:
loop [name-clause] {variable-clause}* {main-clause}* => result*
With
是一个variable-clause
,但是until
是一个termination-test
,这是一个main-clause
。所有 main-clause
必须在所有 variable-clause
之后出现。
FOR部分答案后的WITH:
(loop for x = 0 then (+ x 1)
with y = (+ x 1)
when (> x 10) return (list x y))
这是一系列事件:
- 将建立
X
变量
Y
变量将被建立并初始化为(+ x 1)
X
变量将设置为 0
- ...
可以看到,在操作1)中,X
会是一个变量,但不会初始化为0
。这将在 操作 3) 中发生。因此在操作2)中X
不会被初始化为0
->它的值可能是NIL
。将 1
添加到 NIL
是一个错误。
递归 find-dir-upwards 按预期工作,但 find-dir-upwards-loop 拒绝编译,在 SBCL(Win64、Portacle 1.4、SBCL 2.0.0)下抱怨类型不匹配(即使添加了类型注释) ).
我做错了什么?
(defun find-dir-upwards-loop (dir marker-file)
(loop for prev = nil then curr
for curr = dir then (uiop:pathname-parent-directory-pathname curr)
until (equal curr prev)
with f = (merge-pathnames marker-file curr)
when (uiop:file-exists-p f) return curr))
(defun find-dir-upwards (dir marker-file)
(let ((f (merge-pathnames marker-file dir)))
(if (uiop:file-exists-p f)
dir
(let ((parent (uiop:pathname-parent-directory-pathname dir)))
(unless (equal dir parent)
(find-dir-upwards parent marker-file))))))
简化代码
经过一番思考,我意识到您可以将原始功能简化为:
(defun find-dir-upwards (dir marker-file)
(let ((parent (uiop:pathname-parent-directory-pathname dir)))
(unless (equal dir parent)
(if (probe-file (merge-pathnames marker-file dir))
dir
(find-dir-upwards parent marker-file)))))
其对应的循环函数为:
(defun find-dir-upwards (dir marker-file)
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (probe-file (merge-pathnames marker-file dir))
(return dir)
(setf dir parent))))
正如@Rezo 在probe-file
可以在不同的 Common Lisp 实现之间移植,而 uiop:file-exists-p
则不能。
原回答
我会以不同的方式定义循环。
使用 (let ((dir dir)) (loop ... (setf dir (<some updater function>))))
习惯用法来获取一些引用变量,这些变量在循环中每 运行 更新一次 - 直到满足条件。
(defun find-dir-upwards (dir marker-file)
(let ((f (merge-pathnames marker-file dir))
(dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (uiop:file-exists-p f) ;; if marker-file in dir
(return dir) ;; then arrived at destination
(progn
(setf dir parent) ;; otherwise climb up one dir-level
(setf f (merge-pathnames marker-file dir)))))))
然后进一步分解为:
(defun marker-file-in-dir-p (dir marker-file)
(uiop:file-exists-p (merge-pathnames marker-file dir)))
(defun find-dir-upwards (dir marker-file)
(let ((dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir) ;; parent from dir
unless (equal dir parent)
do (if (marker-file-in-dir-p dir marker-file)
(return dir)
(setf dir parent)))))
将 'FOR' 和 'WITH' 的步骤形式结合起来会产生这个问题。它似乎试图在 'for' 创建 x 之前初始化 'with',但并非总是如此。我还没有解决它,但我以前遇到过这个问题。例如:
(loop for x = 0 then (+ x 1)
with y = (+ x 1)
when (> x 10) return (list x y))
Yields:
Value of X in (+ X 1) is NIL, not a NUMBER.
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "new-repl-thread" RUNNING {10044CC083}>)
Backtrace:
0: (SB-C::%COMPILE-TIME-TYPE-ERROR (NIL) NUMBER #<unused argument> (X) "(+ X 1)" NIL)
1: ((LAMBDA ()))
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LOOP FOR X = 0 THEN ...) #<NULL-LEXENV>)
3: (EVAL (LOOP FOR X = 0 THEN ...))
--more--
但是删除步骤形式或 with 似乎工作正常:
CL-USER> (loop for x from 0 to 10
with y = (+ x 1)
collect (list x y))
((0 1) (1 1) (2 1) (3 1) (4 1) (5 1) (6 1) (7 1) (8 1) (9 1) (10 1))
CL-USER> (loop for x = 0 then (+ x 1)
when (> x 10) return x)
11
您不能(可移植地)在 until
之后放置 with
。看Hyperspec中的语法:
loop [name-clause] {variable-clause}* {main-clause}* => result*
With
是一个variable-clause
,但是until
是一个termination-test
,这是一个main-clause
。所有 main-clause
必须在所有 variable-clause
之后出现。
FOR部分答案后的WITH:
(loop for x = 0 then (+ x 1)
with y = (+ x 1)
when (> x 10) return (list x y))
这是一系列事件:
- 将建立
X
变量 Y
变量将被建立并初始化为(+ x 1)
X
变量将设置为0
- ...
可以看到,在操作1)中,X
会是一个变量,但不会初始化为0
。这将在 操作 3) 中发生。因此在操作2)中X
不会被初始化为0
->它的值可能是NIL
。将 1
添加到 NIL
是一个错误。