机架或方案中的values
和list
或cons
之间有什么区别?什么时候使用一个比使用另一个更好?例如,如果quotient/remainder
返回(cons _ _)
而不是(values _ _)
,会有什么缺点?
早在2002年,George Caswell就在comp.lang.scheme中提出了这个问题。接下来的线索很长,但有很多见解。讨论显示出意见分歧。
https://groups.google.com/d/msg/comp.lang.scheme/ruhDvI9utVc/786ztruIUNYJ
我当时的回答是:
> What are the motivations behind Scheme's multiple return values feature?
> Is it meant to reflect the difference in intent, or is there a
> runtime-practical reason?
I imagine the reason being this.
Let's say that need f is called by g. g needs several values from f.
Without multiple value return, f packs the values in a list (or vector),
which is passed to g. g then immediately unpacks the list.
With multple values, the values are just pushed on the stack. Thus no
packing and unpacking is done.
Whether this should be called an optimization hack or not, is up to you.
--
Jens Axel Søgaard
We don't need no side-effecting We don't need no allocation
We don't need no flow control We don't need no special-nodes
No global variables for execution No dark bit-flipping for debugging
Hey! did you leave the args alone? Hey! did you leave those bits alone?
(Chorus) -- "Another Glitch in the Call", a la Pink Floyd
它们在Scheme和Racket中的语义相同。在这两种情况下,你都需要知道回报看起来如何使用它。
values
连接到call-with-values
,像let-values
这样的特殊形式只是这个过程调用的语法糖。用户需要知道结果的形式才能使用call-with-values
来利用结果。返回通常在堆栈上完成,调用也在堆栈上进行。在Scheme中支持values
的唯一原因是生产者返回和消费者调用之间没有开销。
对于cons
(或list
),用户需要了解返回的数据结构。与values
一样,您可以使用apply
而不是call-with-values
来执行相同的操作。作为let-values
(及更多)的替代品,制作destructuring-bind
宏很容易。
在Common Lisp中,情况大不相同。如果你有更多的信息要提供,你可以总是使用值,如果用户只想使用第一个值,她仍然可以把它作为一个正常的过程来使用。因此,对于CL,您不需要提供quotient
作为变体,因为quotient/remainder
也同样有效。只有当您使用具有多个值的特殊窗体或过程时,该过程确实返回了更多值,这一事实与Scheme的工作方式相同。这使得values
在CL中是比Scheme更好的选择,因为您可以编写一个而不是多个过程。
在CL中,你可以访问这样的散列:
(gethash 'key *hash* 't)
; ==> T; NIL
如果不使用返回的第二个值,则不知道t是默认值还是实际值。在这里,您可以看到第二个值,该值指示在哈希中找不到密钥。通常情况下,如果您知道只有数字,则不使用该值——默认值已经表明没有找到密钥。机架内:
(hash-ref hash 'key #t)
; ==> #t
在球拍中,failure-result
可能是一个thunk,所以你可以通过,但我敢打赌,如果值像在CL中一样工作,它会返回多个值。我认为CL版本和Scheme有更多的内务管理,作为一种极简主义语言,可能不想给实现者额外的工作。
编辑:在发布此之前错过了Alexis对同一主题的评论
与列表相比,使用多个返回值的一个经常被忽视的实际优势是,Racket的compose
"只适用于"返回多个值的函数:
(define (hello-goodbye name)
(values (format "Hello ~a! " name)
(format "Goodbye ~a." name)))
(define short-conversation (compose string-append hello-goodbye))
> (short-conversation "John")
"Hello John! Goodbye John."
compose
生成的函数将把hello-goodbye
返回的两个值作为两个参数传递给string-append
。如果你用函数式风格编写代码,并且有很多组合,这非常方便,而且比用call-with-values
等显式传递值更自然。
这也与您的编程风格有关。如果使用values
,则通常意味着要显式返回n值。使用cons
、list
或vector
通常意味着您希望返回一个包含某些内容的值。
总有利弊。对于values
:它可能在某些实现中使用较少的内存。调用方需要使用let-values
或其他多值特定语法。(我希望我能像CL一样只使用let
。)
对于cons
或其他类型:可以使用let
或lambda
来接收返回值。您需要使用car
或其他过程来明确地解构它,以获得您想要的值。
使用哪种以及何时使用?同样,这取决于您的编程风格和具体情况,但如果返回值不能在一个对象中表示(例如商和余数),那么最好使用values
来使过程的含义更清晰。如果返回值是一个对象(例如,一个人的姓名和年龄),那么最好使用cons
或其他构造函数(例如,记录)。