【问题标题】:How do I do anything with multiple return values in racket?如何在球拍中有多个返回值?
【发布时间】:2013-12-31 15:30:12
【问题描述】:

似乎为了在 Racket 中使用多个返回值,我必须使用define-values 或使用(call-with-values (thunk (values-expr)) list) 将它们收集到一个列表中。在后一种情况下,如果必须将它们收集到一个列表中,为什么有人会选择返回多个值而不是一个列表?此外,这两种方法都非常冗长且难以处理大多数代码。我觉得我一定误解了关于多重返回值的一些非常基本的东西。就此而言,我该如何编写一个接受多个返回值的过程?

【问题讨论】:

  • 是的,我提到了身体中的那些形式,但我的问题是那些真的是我们所拥有的最好的吗?如果是这样,当一个普通的旧列表可以更好、更轻松地完成相同的事情时,为什么我们甚至多个返回值?而且我仍然不知道如何定义一个过程接受返回多个值的结果。
  • 您将此过程定义为带有多个参数的简单 lambda,并通过 call-with-values 调用它:(call-with-values (lambda() (values 1 2)) (lambda(a b) ....))
  • 哇...这就像....正是我在谷歌上搜索的内容。好工作!另外,整洁,我不知道thunk 函数。我一直在制作自己的 lambdas

标签: racket


【解决方案1】:

Racket doc 为我们提供了一个典型的例子,为什么变相:

> (let-values ([(q r) (quotient/remainder 10 3)])
    (if (zero? r)
      q
      "3 does *not* divide 10 evenly"))
"3 does *not* divide 10 evenly"

我们直接得到两个值,并在随后的计算中分别使用它们。

更新:在 Common Lisp 中,凭借其绝对实用、深入人心、非功能性的方法(他们关心每个额外的 cons 单元分配),它使更多感觉,特别是因为它允许人们以“正常”方式调用此类程序,自动忽略“额外”结果,有点像

(let ([q (quotient/remainder 10 3)])
    (list q))

但在 Racket 中,这是无效代码。所以,是的,它看起来像是一个无关紧要的功能,最好完全避免。

【讨论】:

  • 这样我们就不用把它们打包成一个列表了。
  • 那么我的问题是,多个返回值比列表有什么优势?似乎列表可以做同样的事情,除了以更符合语言的方式,并且更易于使用。例如,(编辑:这里有一个实现,无法格式化......哎呀!另外,这个评论是在威尔的前面,但由于技术困难,我不得不删除它并重新发布它)
  • quotient/remainder 不返回列表。它返回两个整数。试试(list (quotient/reminder 10 3))(call-with-values (lambda () (quotient/reminder 10 3)) list)
  • 在 lisp 中抱怨太多的 cons 单元格,在我看来,有点像抱怨沙漠中的一桶沙子太多。在极其抽象的球拍世界中,原语被装箱、拆箱、调整大小、包装,并且通常以所有其他方式“它只是工作”,这似乎很奇怪,这本质上是一个实现细节,不仅是完全可见,但是标准库经常使用的一个重要概念,您也必须使用它。但是,我现在正在玩肥皂盒。感谢您提供信息。
  • 您的答案是唯一直接且中肯的答案。如何从函数中解压缩多个值?就是这样。谢谢!
【解决方案2】:

虽然我可能遗漏了一些 Scheme 历史和其他细微差别,但我会给出我实际的答案。

首先,一条经验法则是,如果您需要返回 2 或 3 个以上的值,请不要使用多个值,也不要使用列表。使用struct。这通常更容易阅读和维护。

Racket 的match 表单使解构列表返回值变得更加容易——就像define-values 一样简单:

(define (f)
  (list 1 2))

(match-define (list a b) (f))
(do-something-with a b)

;; or

(match (f)
  [(list a b) (do-something-with a b)])

如果您有其他函数g,它需要一个(list/c a b),并且您想用f 组合它,如果f 返回一个列表会更简单。如果两者都使用双元素struct,它也会更简单。我认为call-with-values 有点尴尬。

允许多个返回值是一个优雅的想法,因为它使返回值与参数对称。使用多个值也比列表或结构更快(在当前的 Racket 实现中,尽管it could work otherwise)。

但是,当可读性比性能更重要时,在现代 Racket 中,使用liststruct 会更实用,恕我直言。话虽如此,我确实为一次性私有辅助函数使用了多个值。

最后,Racket 邮件列表中出现了long, interesting discussion

【讨论】:

    【解决方案3】:

    values 很方便,因为它

    1. 检查返回的元素数量是否正确
    2. 解构

    例如,使用

    (define (out a b) (printf "a=~a b=~a\n" a b))
    

    然后

    (let ((lst (list 1 2 3)))
      (let ((a (first lst)) (b (second lst))) ; destructure
        (out a b)))
    

    即使 lst 有 3 个元素也可以工作,但是

    (let-values (((a b) (values 1 2 3)))
      (out a b))
    

    不会。

    如果你想对列表进行相同的控制和解构,你可以使用match

    (let ((lst (list 1 2)))
      (match lst ((list a b) (out a b))))
    

    请注意,他创建了结构,例如(list 1 2)(values 1 2) 是等价的。

    【讨论】:

    • values 保证创建结构,如list,还是可以通过 Sylwester 的答案中详述的堆栈机制实现?
    • @WillNess 这是一个实现细节。从语义上讲,它是一个结构,因为它一直保持完整。
    • “语义上在一起”不是一个结构。 :) “结构”有一个非常具体的含义 - 一个缺点单元分配。语义是正交的。当然,这两个值在语义上是相连的,说它们导致结构的创建是完全不同的。 :)
    • @WillNess 结构是数据的表示。它不仅限于 cons 单元;结构也可以在堆栈上表示。见en.wikipedia.org/wiki/Data_structure
    • 我说的是 Lisp 的说法。 :) 而且我们正在比较valueslist... :) 当然,如果一个实现执行使用分析并在堆栈上分配临时conses,那确实是一个非常好的实现。跨度>
    【解决方案4】:

    使用list 作为消费者会破坏多个值的目的,因此在这种情况下,您可以只使用列表开始。多值其实是一种优化方式。

    在语义上返回一个列表和几个值是相似的,但是在一个列表中返回多个值的地方是创建 cons 单元以创建列表和解构访问器以获取另一端的值。然而,在许多情况下,您不会注意到性能上的差异。

    对于多个值,这些值在堆栈上,(call-with-values (lambda () ... (values x y z)) (lambda (x y z) ...) 只检查数字是否正确。如果没问题,您只需应用下一个过程,因为堆栈的参数都是从上一次调用中设置的。

    您可以围绕它制作语法糖,一些流行的语法糖是let-valuesSRFI-8 receive 是一个稍微简单的语法糖。两者都使用call-with-values 作为原语。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-10
      • 1970-01-01
      • 2012-09-10
      • 2014-05-15
      • 1970-01-01
      • 2015-11-24
      • 1970-01-01
      相关资源
      最近更新 更多