如何在球拍中有多个返回值?
Posted
技术标签:
【中文标题】如何在球拍中有多个返回值?【英文标题】: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)
将它们收集到一个列表中。在后一种情况下,如果必须将它们收集到一个列表中,为什么有人会选择返回多个值而不是一个列表?此外,这两种方法都非常冗长且难以处理大多数代码。我觉得我一定误解了关于多重返回值的一些非常基本的东西。就此而言,我该如何编写一个接受多个返回值的过程?
【问题讨论】:
docs.racket-lang.org/reference/values.html ...? 是的,我提到了身体中的那些形式,但我的问题是那些真的是我们所拥有的最好的吗?如果是这样,当一个普通的旧列表可以更好、更轻松地完成相同的事情时,为什么我们甚至有多个返回值?而且我仍然不知道如何定义一个过程接受返回多个值的结果。 您将此过程定义为带有多个参数的简单 lambda,并通过 call-with-values 调用它:(call-with-values (lambda() (values 1 2)) (lambda(a b) ....))
。
哇...这就像....正是我在谷歌上搜索的内容。好工作!另外,整洁,我不知道thunk
函数。我一直在制作自己的 lambdas
【参考方案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 中,使用list
或struct
会更实用,恕我直言。话虽如此,我确实为一次性私有辅助函数使用了多个值。
最后,Racket 邮件列表中出现了long, interesting discussion。
【讨论】:
【参考方案3】:values
很方便,因为它
-
检查返回的元素数量是否正确
解构
例如,使用
(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 的说法。 :) 而且我们正在比较values
和list
... :) 当然,如果一个实现执行使用分析并在堆栈上分配临时conses,那确实是一个非常好的实现。跨度>
【参考方案4】:
使用list
作为消费者会破坏多个值的目的,因此在这种情况下,您可以只使用列表开始。多值其实是一种优化方式。
在语义上返回一个列表和几个值是相似的,但是在一个列表中返回多个值的地方是创建 cons 单元以创建列表和解构访问器以获取另一端的值。然而,在许多情况下,您不会注意到性能上的差异。
对于多个值,这些值在堆栈上,(call-with-values (lambda () ... (values x y z)) (lambda (x y z) ...)
只检查数字是否正确。如果没问题,您只需应用下一个过程,因为堆栈的参数都是从上一次调用中设置的。
您可以围绕它制作语法糖,一些流行的语法糖是let-values
,SRFI-8 receive 是一个稍微简单的语法糖。两者都使用call-with-values
作为原语。
【讨论】:
以上是关于如何在球拍中有多个返回值?的主要内容,如果未能解决你的问题,请参考以下文章