使用let-values代替let有什么好处?

时间:2017-07-28 17:18:36

标签: scheme let chicken-scheme

我心中的这个例子就是:什么更好?

示例1:

(define (foo x)
  ...
  (values a b c))

(let-values (((a b c) (foo  42)))
    ...)

示例2:

(define (foo x)
  ...
  (list a b c))

(let ((f (foo 42)))
  (let ((x (first f)) (y (second f)) (z (third f)))
      ...))

我的粗略猜测是第一种方式是最好的,因为在第二种方式是因为每当有first / second / third的呼叫时,它必须遍历列表。所以我的问题变成:values如何运作?它只是列表的语法糖还是使用其他东西? (例如数组)

如果这取决于实施,我告诉你我正在使用鸡肉计划。

3 个答案:

答案 0 :(得分:2)

关于多值概念来自

的一些背景知识

多个值(通常缩写为“MV”)是Scheme的一个稍微有争议的设计选择。它们使用起来有点笨拙,这会让一些人称它们为“丑陋”。但是它们自然地遵循了类似于程序的界面,这种界面是连续性所提供的。让我解开一下:

每当一个过程“返回”时,真正发生的是过程中最终表达式的值被传递给它的继续。通过这种方式,调用过程的表达式求值为。

例如:在(foo (+ 1 (* 2 6)))中,(* 2 6)的延续将传递其值(即*过程中的最后一个表达式,无论它是什么)作为+的参数{1}}。因此,在(foo (+ 1 <>))中,<>表示(* 2 6)的延续。然后,此示例中(+ 1 (* 2 6))的延续是使用foo调用13(+ 1 (* 2 6))的结果。

如果您使用call-with-current-continuation来捕获此类延续,则会获得一个稍后可以调用的过程。你应该能够用多个参数调用这个过程是很自然的。唯一的问题是表达式如何实际产生多个值。

更具体地说,

(define (get-value) 1)

完全相同:

(define (get-value)
  (call-with-current-continuation
    (lambda (return-the-value)
      (return-the-value 1))))

所以,这也是有道理的:

(define (get-3-values)
  (call-with-current-continuation
    (lambda (cont)
      (cont 1 2 3))))

但是,如何调用get-3-values?每个表达式总是产生一个值(换句话说,每个常规过程调用的连续只接受一个值)。这就是为什么我们需要一个特殊的构造来创建一个实际上接受多个值的延续:let-values来自SRFI-71(或receive来自SRFI-8)。

如果您只想使用标准结构,可以使用call-with-values,但这有点尴尬:

(call-with-values (get-3-values) (lambda (a b c) ...))

TL;博士

如果您可以调用具有多个值(参数)的过程,为什么它不能返回多个值?这是有道理的,特别是考虑到Scheme处理continuation的核心,这会模糊“argument”和“return value”之间的界限。

设计选择:何时使用MV,何时不使用?

现在,关于何时使用多个值而不是显式列表是一个品味问题。如果该过程在概念上具有多个返回值,则返回多个值最有意义。例如,如果您获得PDF中页面的尺寸,则库可以具有get-dimensions过程,该过程返回两个值:宽度和高度。将它们放回列表中会有些奇怪。当然,在这种特殊情况下,将设计更改为get-heightget-width可能更有意义,但如果计算操作的成本很高并且产生两个值,则更有意义{ {1}}而不是单独的(因为这需要两倍的计算时间或一些复杂的缓存/记忆)。

特别是在CHICKEN中,还有一个设计原因是使用多个值返回:它隐式丢弃所有值,但第一个如果多个值传递给只接受一个值的延续(在标准Scheme中,它是一个错误“如果是发生了,所以实现可以自由地做任何他们想做的事情。

对于一个实际的例子,http-client egg(免责声明:我是它的作者)有像get-dimension这样的过程,它们会返回几个值,但第一个值通常是最有用/最有趣的:这就是从URI读取的结果。返回的其他值是请求的URI(如果存在任何重定向,则可能会有所不同)和响应对象,如果需要从响应中提取标头,这有时很有用。所有这一切的结果是,您可以with-input-from-request接收包含Stack Overflow主页的字符串,甚至无需处理构建请求对象或分离响应对象,但如果需要,您可以选择这样做做更高级的事情。

请注意,在某些方案中可能无法有效地实现多个值,因此出于实际原因,返回像列表,向量或记录这样的对象可能更有效(在CHICKEN中,MV很快,因此不是问题)。 / p>

答案 1 :(得分:1)

从语义的角度来看,没有一个比另一个好。您可以使用列表,向量和记录,它们将提供相同的功能。 IMO哈希值稍微有利,因为那时你有访问器,因此可以在以后增加接口。 JavaScript只能返回一个值,因此它使用对象,而新的重组运算符使它变得非常实用。

除了词汇封闭之外,Scheme并没有发明太多,因此从早期的lisp版本中获得了很多。就像loop宏能够返回多个值意味着您不需要在堆上分配需要GC-ing的短期对象。当时它的机器内存非常少,GC-ing有时也会用磁盘完成。在机器级别上,返回通常是堆栈或寄存器中的内容。然后,多个值将是堆栈上的两个值或使用两个寄存器。没有用于封装的堆分配意味着没有短期内存的分配。

在兄弟语言Common Lisp中,多个值更有用,因为每个函数都可以在一个值的位置使用,然后只使用返回的第一个值。您可以通过Scheme中的标准库看到缺少这一点,其中有几个函数在执行相同操作。 quotient/remainder返回两个值,quotientremainder返回两个值中的每一个。

答案 2 :(得分:0)

多个返回值实际上与列表不同,这有点奇怪。 valueslet-valuesdefine-values等用于处理多个返回值。

如果您使用模式匹配宏,例如从match鸡蛋中,你可以在返回列表时做同样的事情。