【问题标题】:Returning from a function inside when statement从 when 语句中的函数返回
【发布时间】:2015-04-29 03:54:51
【问题描述】:

我要做的就是使用 when 语句返回一个值 :( 我想要以下功能:

if(x)
    return y

我正在尝试使用:

(when (x) y)

但是 when 语句没有以退出函数并返回 y 的方式进行评估。它只是愉快地继续下一行。有没有办法做到这一点,而不会制作一个非常难看的 if-else 块? mzscheme/racket 不允许 1-armed ifs。

【问题讨论】:

  • x 计算为#f 时,代码应该做什么?
  • 我认为这是一个 XY 问题。如果你想在x 为真时退出函数并继续,否则你就有了 if 的定义。

标签: scheme lisp racket


【解决方案1】:

Racket 中的一个简单方法:

(define (foo x)
  (let/ec return
    (when (= x 0)
       (return 'zero))
    'not-zero))

这里的 ec 代表转义延续,它比完全延续便宜。

【讨论】:

    【解决方案2】:

    好吧,我要成为“那个人”;有充分的理由使用“极其丑陋”的解决方案,将函数的其余部分放在条件的“else”中;它使代码更易于阅读和理解。当我试图了解一个函数的作用时,我不想扫描所有代码寻找隐藏的returns 和奇怪的控制流。一个简单的 'if' 或 'cond' 可以非常清楚地说明每段代码将在什么情况下使用。

    如果您认真思考为什么喜欢“何时+返回”解决方案,我怀疑在某种程度上,您想采取这种措施并“让其远离”您的认知过程,让其余的成为焦点的功能。这(我声称)是导致微妙错误的秘诀。

    搜索你的感受;你知道这是真的!

    编辑:一只蚂蚁刚刚爬过我的笔记本电脑。这表明我说的是实话。

    【讨论】:

      【解决方案3】:

      您将其标记为 Common Lisp 和 Racket,它们是两种完全不同的语言。如果您正在使用 Racket 或 Scheme 并希望尽早从函数中返回,则可以使用 continuation:

      (define (my-function x y)
        (call-with-current-continuation
          (lambda (return)
            (when x (return y))
            ;; Rest of code not evaluated if X is true
            )))
      

      在包括 Racket 在内的一些 Scheme 实现中,call-with-current-continuation 也绑定到 call/cc,但 call-with-current-continuation 是使用延续的唯一可移植方式。

      上面的内容比使用cond 语句还要难看。如果你想摆脱所有多余的废话,你可以定义一个宏来创建define 的替代版本,它会自动创建延续并将其绑定到return

      (define-syntax define/return
         (syntax-rules ()
            ((_ (name . args) . body)
             (define (name . args)
               (capture-vars (return)
                  (call/cc (lambda (return) . body)))))))
      

      这需要您拥有我的capture-vars 宏,您可以在this answer 中找到它。

      编辑:Leppie 提供了define/return 的以下实现,这要简单得多,因为它不需要我的capture-vars 宏:

      (define-syntax define/return
        (lambda (x)
          (syntax-case x ()
            [(_ (name . args) . body)
              (with-syntax
                ([return (datum->syntax #'name 'return)])
               #'(define (name . args)
                   (call/cc (lambda (return) . body))))])))
      

      编辑 2:但是,如果您将 define/return 合并到另一个宏中,则很容易以这种方式意外取消捕获 return 的定义。

      然后return 将按照您的预期行事,并且不会在语法上令人反感:

      (define/return (my-function x y)
          (when x (return y))
          ;;; more code...
      )
      

      但是,如果您使用的是 Common Lisp,情况就不同了。在 Common Lisp 中,(return y) 只有在定义了名为 nilblock 时才会编译。某些形式隐式定义了一个名为nil 的块,例如loop 宏。如果没有名为 nil 的块,您仍然可以使用 return-from 从命名块返回。如果您在使用defun 定义的函数中,则该函数的名称也是包装该函数的块的名称,所以这会起作用:

      (defun my-function (x y)
         (when x (return-from my-function y))
         ;;; more code
         )
      

      【讨论】:

      • 你为什么要费尽心思去捕获return 标识符? gist.github.com/leppie/887a278e4db0db99b031 之类的东西在 R6RS 中运行良好。我错过了什么吗?
      • @leppie 不。你没有错过任何东西。我其实不知道你能做到这一点。
      • 在 Racket 中,在宏内部赋予特殊关键字含义的适当方法是使用语法参数。 (define-syntax-parameter return #f) (define-syntax-rule (define/return header body ...+) (define header (let/ec return-continuation (syntax-parameterize ([return (make-rename-transformer #'return-continuation)]) body ...)))) 这避免了破坏卫生或需要使用 syntax-case / syntax-parse 而不是更简单的 define-syntax-rule
      • @Jack 语法参数可以被局部变量遮蔽,使得它们不比全局变量好。我详细介绍了它here
      • @ThrowawayAccount3Million 为什么隐藏语法参数的能力是一个的事情?这意味着他们尊重词汇范围。如果我这样做了(splicing-let ([return something-else]) (define/return ...)),我会期望 return 引用范围内最接近的返回定义。语法参数不用于将宏绑定到某个全局标识符,它们用于明确说明宏依赖于某些上下文绑定。词法范围的绑定是最容易推理的绑定。
      【解决方案4】:

      简单:

      (and (x) y)
      

      问题是“when”没有返回值。 “和”可以。

      【讨论】:

      • 不,这是不正确的,很遗憾。在这个问题的上下文中,两者都做同样的事情 - 都“愉快地继续到下 [code] 行”并且不立即从函数返回。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-06
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      相关资源
      最近更新 更多