【问题标题】:How to use Lazy Evaluation in Guile scheme?Guile 方案中如何使用惰性求值?
【发布时间】:2016-12-11 05:18:59
【问题描述】:

我写了一段代码,它使用惰性求值来产生无限的数据结构,但是有一个错误。

代码如下:

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n (force n1)))))
(define (main args)
    (display (car (ints-f 3) ))
    (newline)
    )

这给出了一个错误,说堆栈溢出。这意味着即使没有调用强制也正在执行。如何解决这个问题?

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n n1))))
(define (main args)
    (display (car (ints-f 3) ))
    (newline)
    )

上面的代码给出了 3 的预期输出,但是如果我在下面的代码中使用 cdr

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n n1))))
(define (main args)
    (display (cdr (ints-f 3) ))
    (newline)
    )

它打印一个承诺对象。

如何在 guile 方案中使用惰性求值?

【问题讨论】:

  • 如果你在 delay 之后紧接着 force,那么你就绕过了 Promise 的惰性属性。 delay 表单有效地创建了一个 thunk,force 调用它。唯一的区别是承诺会缓存它们的结果,因此两次强制相同的承诺不会重新评估计算。整个语言的语义仍然非常严格。

标签: scheme lazy-evaluation guile


【解决方案1】:

您是否正在尝试创建流?您可能希望咨询(srfi srfi-41) 模块以了解其实现。 (披露:我编写了模块代码的 Guile 特定部分;其他所有内容都从 the reference implementation 移植。)

(use-modules (srfi srfi-41))
(define-stream (ints-f n)
  (stream-cons n (ints-f (1+ n))))

请注意,define-streamstream-cons 是在幕后协同工作以构建(SRFI 45 风格)delay/force 的宏。

使用示例:

> (stream->list 10 (ints-f 100))
(100 101 102 103 104 105 106 107 108 109)

特别是,您的函数扩展为:

(define (ints-f n)
  (lazy (eager (cons (delay n)
                     (lazy (ints-f (1+ n)))))))

您可以使用:

> (define x (ints-f 100))
> (force (car (force x)))
100
> (force (car (force (cdr (force x)))))
101

【讨论】:

  • “扩展”,还是“如果用stream-cons 重写会扩展”? OP的功能很简单(cons n (delay ...)),是吗?
  • @WillNess (cons ... (delay ...)) 创建奇数流,而 (delay (cons ...)) 创建偶数流。奇数流的问题是你总是将一个元素物化太多。使用偶数流,您可以根据需要实现尽可能多的元素。有关详细信息,请参阅SRFI 41 的基本原理部分。
  • 是的,我已经看到了,但我看到的只是基于对 take 的错误过度渴望实现。 (参见this)。正确编码很容易,然后反对意见就消失了。一件事是流,其中每个元素都是独立强制的;另一个是到达一个元素会自动强制所有以前的元素;两者都有自己的位置;我想后者应该更常用,当然是正确的take
  • (奇怪的流也可能使编码人员在编码/调用诸如累积等,IIRC 之类的 HOF 时不得不使用显式延迟,但这并不是禁止的。---这里是an example of that, and "properly" coded take too ---这也意味着简单的(lambda() ...) 延迟就足够了 --- 这简化了整个事情,并且一定会更有效率。)
【解决方案2】:

第一个 sn-p 不正确,您在构建序列时强制使用该值,因此违背了惰性求值的整个目的。另一方面,第二个 sn-p 看起来是对的——尽管它可以简化一点:

(define (ints-f n)
  (cons n (delay (ints-f (+ n 1)))))

调用cdr 时得到一个promise 是正常,它是一个用delay 构建的thunk,只有在forced 时才会产生它的值。事实上,如果你想窥探无限序列的元素,你必须使用一个过程来遍历你感兴趣的部分:

(define (take seq n)
  (if (= n 0)
      '()
      (cons (car seq)
            (take (force (cdr seq)) (- n 1)))))

同样:

(define (get seq idx)
  (if (= idx 0)
      (car seq)
      (get (force (cdr seq)) (- idx 1))))

例如:

(take (ints-f 5) 5)
=> '(5 6 7 8 9)

(get (ints-f 5) 5)
=> 10

【讨论】:

  • 你的take 对通过一个额外的元素过度强制序列进行了经典的疏忽。 (take seq 1) 应该等价于(list (car seq)),而不是(begin (force (cdr seq)) (list (car seq)))。 :)
猜你喜欢
  • 1970-01-01
  • 2020-09-10
  • 2016-11-29
  • 1970-01-01
  • 2018-10-08
  • 1970-01-01
  • 2016-01-18
  • 1970-01-01
  • 2019-09-22
相关资源
最近更新 更多