在Lisp中是否有类似于C语言return语句的东西?

6

我希望在Lisp中实现一个基本条件的递归函数,但是由于Lisp中没有返回语句,我无法做到这一点。

我的Lisp代码是基于这个C代码编写的。

if (n==0) return;

我该如何在Lisp中实现这个功能?

Lisp经常被用作函数式编程语言。这意味着对term/stdout的输出被认为是“副作用”,有些(没有太强烈的负面含义)不干净。当然,Common Lisp中的任何函数都会评估到其最终形式的值。 - miercoledi
在一个名为 #'foo 的函数中,如果要像“如果 n 等于 0,则中止所有操作”这样的操作,通常会使用条件语句 - 在大多数情况下,这可能是 #'cond 函数,但这里有一个使用 #'when 的简单实现,它通常是内置的宏。 (when (= n 0) (return-from foo nil)) 在上面的 when 表单中,nil 可以被更复杂的表单替换,但如果需要,您可能希望构建 (cond)(case) 等的实现。 - miercoledi
@miercoledi Lisp并不比Perl更加函数化,除了引述更简单外。有些问题无法仅通过函数式的编程方式解决,这时需要引入状态机来完成和命令式代码相同的操作,只不过环境中的转换会在每次操作后进行复制。其中一个这样的问题是部分规约,您想要在转换环境的同时减少一系列步骤,但在满足某个条件时停止减少;“命令式”代码可以将这种复杂性有效地抽象出来,而“纯函数式”则使其变得不切实际。 - Dmytro
纯函数式方法在表达“在我的数组xs中查找字符串”hi”的索引时遇到困难,因为即使在找到它后reduce仍必须遍历整个数组,而filter将遍历整个数组,甚至不知道第一个元素是什么,因为它旨在执行无序操作。因此,如果不引入状态机/流/“单子”,则没有正确表达这个问题的方法,而这种方法等同于命令式方法。 - Dmytro
3个回答

8

Common Lisp 有一个特殊形式叫做 RETURN-FROM (以及它的相关形式 RETURN),用于实现你想要的功能。

(defun accumulate-list (list)
  (when (null list)
    (return-from accumulate-list 0))
  (+ (first list)
     (accumulate-list (rest list))))

话虽如此,我更喜欢在编写递归函数时使用COND

(defun accumulate-list (list)
   (cond
     ((null list)
      0)
     (t
      (+ (first list)
         (accumulate-list (rest list))))))

请注意,如果cond中的一个子句没有主体形式,则如果测试表达式返回非nil值,则返回测试表达式的结果。这意味着您可以编写(cond ((null list) 0) ((+ (first list) ...)));在这种情况下不需要t - Joshua Taylor
1
@JoshuaTaylor(我的评论不适用于CL。)在Scheme中,最好使用else而不是将表达式用作测试形式,因为else表达式处于尾部位置,而测试形式仍然必须经过真实性测试。 - C. K. Young
@ChrisJester-Young 关于Scheme/CL的差异,你说得很好;问题只是标记为[tag:lisp],所以不清楚目标Lisp是什么,但这个答案使用了defun,所以我假设是Common Lisp。如果有后续子句,我可以理解为什么独立测试会处于非尾位置,但如果它是最后一个子句,那么独立测试不就处于尾位置了吗?例如,在(cond ... ((+ 2 3)))中,如果你一直到达(+ 2 3),那么它将成为返回值。这就像(and ... last)(or ... last)的情况,不是吗? - Joshua Taylor
在Scheme中,#f(在标准术语中是唯一的false值)和“未指定的值”之间存在差异(如果cond“掉落”最后一个子句,则返回该值,并且通常为实现提供的(void))。因此,在Scheme中,仅用于测试的最后一个子句不能简单地优化为else。(但我可以看到您如何在CL中执行此操作,因为nil既是false值,也是如果执行掉落,则是cond的结果)。 - C. K. Young
@ChrisJester-Young 我正在查看 R5RS,4.2.1 条件语句 并且发现每个子句都是 (<test> <expression> ...),所以(正如你过去提醒我的那样 ;)),可以有 个表达式。之后,它说,“如果所选子句仅包含测试而没有表达式,则测试的值将作为结果返回。” 因此 Scheme 确实 支持 (cond ... (test)) 求值为 test 求值的任何内容。(它是否处于尾位置仍然未知。) - Joshua Taylor
显示剩余5条评论

4

对于 Algol 程序员(或其许多方言,如 C、Java、Perl 等),LISP 中的每个表达式都像是一个“返回表达式”。例如:

{
  int x = 10;
  if( x == 10 )
    return 10 * 5;
  else
    return 5 * 19; 
}

在LISP中,可以这样写:
;; direct version
(let ((x 10))
  (if (= x 10)
      (* 10 x)
      (* 5 x)))


;; if can be anywhere
(let ((x 10))
  (* x
     (if (= x 10) 
         10 
         5))))

作为你可能注意到的,LISP 的 if 更像是三元运算符 ( expression ? consequent : alternative ) 而不是 C 的 if
编辑:
现在您已经添加了一个使用 C 中的 return 的示例要翻译,我看到您并没有使用 return 返回一个值,而是将其作为 goto 早期退出函数。由于 goto 仍然被认为是有害的,即使 CL 的 return-from 肯定是最好的直译,但它并不总是正确的答案。
在任何 LISP 中,您都需要提供要返回的值,即使您不打算使用它(对于因其副作用而调用的函数)。如果您不打算使用该值,则可以只使用 nil
(if (zerop x)
    nil
    (something-else x))

如果您需要多个语句(用于副作用),则使用letprogn或将整个内容切换为cond

;; using let to bind as well as implicit progn
(if (zerop x)
    nil
    (let ((tmp (gethash x *h*)))
      (setf (gethash x *h*) (+ x 1))
      (something-else tmp)))

;; using progn
(if (zerop x)
    nil
    (progn
      (setf (gethash x *h*) (+ (gethash x *h*) 1))
      (something-else (gethash x *h*))))

;; using cond
(cond ((zerop x) nil)
      (t
       (setf (gethash x *h*) (+ (gethash x *h*) 1))
       (something-else (gethash x *h*))))

2
我最近在某个地方读到,当Dijkstra发表那篇文章时,被认为有害的GOTO是自由跳入和跳出代码块的那种(使得这样的块在概念上具有多个入口和出口点),实际上在那个时候,即使在汇编语言中编码时,子程序也被认为是一种新颖的范例(函数调用协议只是程序员要遵守的约定)。因此,可以说(并且我将其翻译回我们的问题)在算法上使用return-from是没有问题的。 :) - Will Ness
@WillNess 我喜欢它仍被认为是有害的这个概念,以及存在例外情况。这样你每次写代码时都会思考它。这就像 Scheme 中的 call/cc,我几乎从不使用它。 - Sylwester
1
我个人喜欢结构化的本地化goto语句。就像他们所说的那样,你的情况可能有所不同。:) 最近我在C中编写了一些循环,其中有两个入口点,有时需要执行一些内部初始化(基本上是按块工作,但产生一个平面输出,遵循concatMap ... span ...范例)。对我来说,与标志打交道似乎更加混乱。 - Will Ness

1
你只需将if语句放在整个代码块前面,并在需要返回“nothing”时使用nil而不是return
因此,要计算从0到n的所有数字的总和:
(defun somefunc (n)
  (if (zerop n)
      0
      (+ n (somefunc (- n 1)))))

如果 n==0,递归函数返回 0。否则,它执行加法,将 n 加到 f(n-1) 上,并返回该结果。(请注意,这不是理想的算法,只是一个递归函数的示例)。
请记住,Lisp 返回函数中最后执行的表达式的值作为其值。如果 n==0,则上述代码返回 0。如果 n>0,它将返回 + 表达式的结果。
如果您需要多步测试(例如确保未传递负数),cond 就是方法(如前所述)。无论哪种方式,最后一个执行的东西的值都是函数的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接