【问题标题】:Lisp exit defun function with nil as valueLisp 以 nil 为值退出 defun 函数
【发布时间】:2021-05-09 11:10:54
【问题描述】:

我正在尝试执行名为positionRec 的函数位置的递归版本。目标是定义一个元素在列表中的位置,如果该元素不在列表中,则返回“nil”。举个例子: (positionRec 'a '(b c d a e)) => 4 (positionRec '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)))) ) )

我没有成功返回 nil。我有一个错误"*** - return-from: no block named nil is current is visible"

谁能教我怎么做?

【问题讨论】:

  • 有时候比你最初想象的要容易????只需跳过return 并使用单个nil 就可以了。
  • 你可能想让这个尾递归(一些 lisp 有尾调用优化,不给他们施展魔法的机会是浪费):(defun osition-rec (c l) (labels ((iter (i lst) (if lst (if (equal c (first lst)) i (iter (+ i 1) (rest lst))) nil))) (iter 1 l)))

标签: recursion lisp common-lisp


【解决方案1】:

Lisp 是一种表达式语言:它只有表达式而没有语句。这意味着对函数的调用的值只是该调用中涉及的最后一种形式的值 这与许多同时具有语句和表达式的语言不同,并且您必须使用显式 returns 显式地乱扔代码说一下函数调用的值是什么。

cond 形式又是一个表达式。像

这样的表达式的值
(cond
 (<test1> <test1-form1> ... <test1-formn>)
 (<test2> <test1-form1> ... <test1-formn>)
 ...
 (<testn> <testn-form1> ... <testn-formnn>))

是第一个 &lt;testm&gt; 的 &lt;testm-formn&gt; 是真的,或者 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 ...),并且各种结构将blocks 命名为nil 包裹在它们的身体周围。两个这样的,实际上是dotimes 和dolist,所以如果你想早点摆脱一个大循环,你可以这样做:

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

但总的来说,由于 Lisp 是一种表达式语言,因此您需要使用 return / return-from 的频率比使用其他一些语言要少得多。

在您的情况下,修改后的函数将失败:如果您遇到 ((atom l) nil) 情况,那么它将 nil 返回其父级,这将...尝试将 1 添加到其中。更好的方法是计算您的位置:

(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 开始的索引:从零开始将与 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)))

如果你想让它更简洁一点,你可以使用iteration macro:

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

【讨论】:

  • 哦。了解索引,我将更改它。并感谢本地函数和迭代宏的示例。我学到了新东西。
【解决方案2】:

主要问题是您正在尝试处理不可通约的值。一方面,你想处理数字,另一方面,你想处理空列表。您不能将数字添加到列表中,但您会自然而然地尝试这样做(您在 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 进行比较”或“适合尾调用消除的版本”,然后简单地选择你找到的那个最易读。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-19
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    相关资源
    最近更新 更多