以nil为值的Lisp出口defun函数



我正在尝试做一个名为positionRec的函数位置的递归版本。目标是定义元素在列表中的位置,如果该元素不在列表中,则返回"0";零";。例如:(位置Rec‘a’(b c d a e((=>4.(位置Rec‘a’(b c d e((=>无

我写过:

(defun positionRec (c l)
(cond
((atom l) (return nil))
((equal c (first l)) 1)
(t (+ 1 (positionRec c (rest l)))) ) )

我没有成功返回零。我有一个错误"***-return from:当前没有名为nil的块可见">

有人能教我怎么做吗?

Lisp是一种表达式语言:它只有表达式,没有语句。这意味着对函数的调用的值只是该调用中所涉及的最后一个形式的值。这与许多既有语句又有表达式的语言不同,在许多语言中,你必须用显式returns显式地填充代码来说明函数调用的值。

cond形式又是一个表达式。像这样的表达式的值

(cond
(<test1> <test1-form1> ... <test1-formn>)
(<test2> <test1-form1> ... <test1-formn>)
...
(<testn> <testn-form1> ... <testn-formnn>))

是第一个为真的<testm><testm-formn>,或者如果它们都不是,则为nil(作为特殊情况,如果在测试之后没有为真的形式,则该值为该测试的值(。

因此,在你的代码中,你只需要确保测试中最后一个成功的表单是你想要的值:

(defun positionRec (c l)
(cond
((atom l) nil)
((equal c (first l)) 1)
(t (+ 1 (positionRec c (rest l))))))

那么,return有什么用呢?好吧,有时你真的想说‘好吧,在一些复杂的循环或其他事情的中间,我现在完成了’:

(defun complicated-search (...)
(dolist (...)
(dolist (...)
(dotimes (...)
(when <found-the-interesting-thing>
(return-from complicated-search ...))))))

return本身简单地等同于(return-from nil ...),并且各种构造将block命名为nil包裹在它们的身体周围。事实上,有两个是dotimesdolist,所以如果你想尽早逃离一个大循环,你可以这样做:

(defun complicated-search (...)
(dolist (...)
(when ...
(return 3))))                     ;same as (return-from nil 3)

但总的来说,因为Lisp是一种表达式语言,所以您需要使用return/return-from的频率比在其他一些语言中要低得多。

在您的情况下,修改后的函数将失败:如果您遇到((atom l) nil)的情况,那么它将把nil返回给它的父函数,它将。。。尝试将CCD_ 18添加到其中。一个更好的方法是记下你在哪里:

(defun position-of (c l)
(position-of-loop c l 1))
(defun position-of-loop (c l p)
(cond
((atom l) nil)
((equal c (first l)) p)
(t (position-of-loop c (rest l) (1+ p)))))

请注意,这(与您的原始版本一样(使用基于1的索引:基于0的索引将与CL的其余部分更兼容

position-of-loop设为本地函数可能是惯用的做法:

(defun position-of (c l)
(labels ((position-of-loop (lt p)
(cond
((atom lt) nil)
((equal c (first lt)) p)
(t (position-of-loop (rest lt) (1+ p))))))
(position-of-loop l 1)))

如果你想让它更简洁一点,你可以使用迭代宏:

(defun position-of (c l)
(iterate position-of-loop ((lt l) (p 1))
(cond
((atom lt) nil)
((equal c (first lt)) p)
(t (position-of-loop (rest lt) (1+ p))))))

主要问题是您试图处理不可通约的值。一方面,你想处理数字,另一方面,处理空列表。您不能将号码添加到列表中,但您会尝试这样做(在cond的默认分支中有一个无条件的(1+ ...)呼叫(。

有很多方法可以解决这个问题,其中一种是获取价值:

(cond
...
(t (let ((val (positionRec c (rest l))))
(when val ;; Here we "pun" on nil being both false and the "not found" value
(1+ val)))))

另一种方法是使用一种适合尾部递归的方法:

(defun positionrec (element list &optional (pos 1))
(cond ((null list) nil)
((eql element (head list)) pos)
(t (positionrec element (rest list) (1+ pos)))))

第二个函数(使用一个足够智能的编译器(基本上可以变成一个循环。它的工作方式是将返回值作为可选参数进行传递。

你可以使用return构建一个版本,但你可能需要使用labels来直接实现(如果你直接从函数返回nil,它仍然会在(1+ ...)中结束,在那里你有数字不兼容(,所以我会选择";明确地捕获该值并与nil/false进行比较";或";适用于尾部呼叫消除的版本";只需选择一个你觉得最可读的。

最新更新