问题 如何在球拍中使用多个返回值执行任何操作?


看来为了在Racket中使用多个返回值,我必须要么使用 define-values 或者将它们收集到列表中 (call-with-values (thunk (values-expr)) list)。在后一种情况下,为什么有人会选择返回多个值而不是列表,如果只是必须将它们收集到列表中呢?此外,这些都非常冗长和笨拙,无法在大多数代码中使用。我觉得我必须误解一些关于多重返回值的基本信息。就此而言,我该如何编写程序 验收 多个返回值?


2686
2017-12-13 00:02


起源

docs.racket-lang.org/reference/values.html ...? - Will Ness
是的,我在体内提到了那些形式,但我的问题是那些真正最好的吗?如果是这样,为什么我们甚至 有 当一个普通的旧列表可以更好,更容易地做同样的事情时,多个返回值?我仍然不知道如何定义程序 验收 返回多个值的结果。 - Matt G
您将此过程定义为具有多个参数的简单lambda,并通过call-with-values调用它: (call-with-values (lambda() (values 1 2)) (lambda(a b) ....))。 - Will Ness


答案:


虽然我可能会遗漏一些计划历史和其他细微差别,但我会给你实际答案。

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

球拍的 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实现中) 它可能会起作用)。

然而,当可读性比性能更高时,那么在现代Racket中使用a更为实用 list 或者a struct, 恕我直言。话虽如此,我确实为一次性私人助手功能使用了多个值。

最后,还有一个 长而有趣的讨论 在Racket邮件列表上。


7
2017-12-13 15:04





运用 list 因为消费者违背了多个价值观的目的,所以在这种情况下你可以使用列表开始。多个值实际上是一种优化方式。

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

使用多个值时,值位于堆栈上 (call-with-values (lambda () ... (values x y z)) (lambda (x y z) ...) 只检查数字是否正确..如果没关系,你只需应用下一个程序,因为堆栈的所有参数都是从前一次调用中设置的。

你可以围绕这个制作语法糖,一些流行的是 let-values 和 SRFI-8收到 是一个稍微简单的一个。两种用途 call-with-values 原始的。


2
2017-12-13 00:57





values 很方便,因为它

  1. 检查返回的元素数是否正确
  2. destructures

例如,使用

(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) VS (values 1 2) 是等价的。


2
2017-12-13 13:28



是 values 保证创建结构,如 list,还是可以通过堆栈机制实现,如Sylwester的答案中所详述的那样? - Will Ness
@WillNess这是一个实现细节。从语义上讲,它是一种结构,因为它始终保持完整。 - uselpa
“语义上在一起”不是一个结构。 :)“结构”有一个非常具体的含义 - 缺点细胞分配。语义是正交的。当然这两个值在语义上是连接的,说它们导致结构的创建是完全不同的。 :) - Will Ness
@WillNess结构是数据的表示。它不仅限于cons细胞;结构也可以在堆栈上表示。看到 en.wikipedia.org/wiki/Data_structure。 - uselpa
我说的是Lisp的说法。 :) 和我们 是 比较 values 和 list... :)当然,如果一个实现执行使用分析并在堆栈上分配短暂的conses,那确实是一个非常好的。 - Will Ness


球拍文件 给我们一个典型的例子,为什么,伪装:

> (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中,凭借其明确实用的,实用的,非金属的,非功能性的方法(它们关注每个额外的缺陷单元分配),它更有意义,特别是因为它允许一个人调用这样的程序一种“正常”的方式,自动忽略“额外”的结果,有点像

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

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


2
2017-12-13 00:22



这样我们就不必将它们打包在列表中。 - Will Ness
那么我的问题是,多个返回值对列表有什么好处?似乎列表可以做同样的事情,除了以更符合语言的方式,并且更容易使用。例如,(编辑:在这里有一个实现,无法让它格式化......哎呀!而且,这个评论在Will的面前,但由于技术上的困难,我不得不删除它并重新发布) - Matt G
quotient/remainder 不返回列表。它返回两个整数。尝试 (list (quotient/reminder 10 3)) 和 (call-with-values (lambda () (quotient/reminder 10 3)) list)。 - Will Ness
在我看来,在lisp中哀悼过多的利弊细胞有点像抱怨沙漠中有太多沙子的沙漠。在极其抽象的球拍世界中,原始体被盒装,未盒装,重新调整大小,包裹,并且通常以其他所有方式“只是工作”,这似乎很奇怪,这本质上是一个实现细节,不仅仅是完全可见,但标准库经常使用的一个重要概念,您也必须使用它。但是,我现在正在使用肥皂盒。感谢您的信息。 - Matt G
你的答案是直接和重点的唯一答案。如何从函数中解压缩多个值?这是怎么回事。谢谢! - Kaushik Ghose