使用 let-values 而不是 let 有什么好处?

And*_*eri 3 scheme let chicken-scheme

我脑海中的例子是:什么更好?

示例 1:

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

(let-values (((a b c) (foo  42)))
    ...)
Run Code Online (Sandbox Code Playgroud)

示例 2:

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

(let ((f (foo 42)))
  (let ((x (first f)) (y (second f)) (z (third f)))
      ...))
Run Code Online (Sandbox Code Playgroud)

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

如果这取决于实施,我会让您知道我正在使用鸡肉计划。

sja*_*aan 5

多值概念从何而来的一些背景

多值(通常缩写为“MV”)是 Scheme 的一个略有争议的设计选择。它们使用起来可能有点笨拙,这导致一些人称它们为“丑陋”。但它们自然地遵循具体化延续提供的类似过程的界面。让我稍微拆开一下:

每当过程“返回”时,真正发生的是过程中最终表达式的值被传递给它的延续。这样,调用过程的表达式的计算结果就是一个值。

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

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

更具体地说,

(define (get-value) 1)
Run Code Online (Sandbox Code Playgroud)

完全一样:

(define (get-value)
  (call-with-current-continuation
    (lambda (return-the-value)
      (return-the-value 1))))
Run Code Online (Sandbox Code Playgroud)

所以,这也有道理:

(define (get-3-values)
  (call-with-current-continuation
    (lambda (cont)
      (cont 1 2 3))))
Run Code Online (Sandbox Code Playgroud)

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

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

(call-with-values (get-3-values) (lambda (a b c) ...))
Run Code Online (Sandbox Code Playgroud)

tl;博士

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

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

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

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

举一个实际的例子,http-client egg(免责声明:我是它的作者)有类似with-input-from-request返回几个值的过程,但第一个值通常是最有用/最有趣的:这是从 URI 读取的结果。返回的其他值是请求的 URI(如果有任何重定向可能会有所不同)和响应对象,如果您需要从响应中提取标头,这有时很有用。所有这一切的结果是,您可以(with-input-from-request "https://stackoverflow.com" #f read-string)接收包含 Stack Overflow 主页的字符串,甚至不必费心处理构建请求对象或挑选响应对象,但是如果您需要做更高级的事情,您可以选择这样做.

请注意,在某些 Scheme 中,多个值的实现可能效率低下,因此出于实际原因,返回一个对象(如列表、向量或记录)可能更有效(在 CHICKEN 中,MV 速度很快,因此这不是问题)。