我试图看看如何重新绑定词法绑定,或重新定义关闭一个lambda。 next-noun的预期用法只是根据需要多次调用它而没有参数。它应该回归……
你的问题是 eval 。 eval 没有词汇环境从它被调用的地方而不是它最多具有顶级绑定。例如。
eval
(define x 12) (let ((x 10)) (eval '(+ x x))) ; ==> 24
eval 几乎总是错误的解决方案,通常可以用闭包替换,直接或用 apply 。这是我要做的:
apply
(define (shuffle-generator lst) (define shuffled (shuffle lst)) (define (next-element) (when (null? shuffled) (set! shuffled (shuffle lst))) (begin0 (car shuffled) (set! shuffled (cdr shuffled)))) next-element) (define next-int15 (shuffle-generator '(1 2 3 4 5))) (define random-bool (shuffle-generator '(#t #f))) (random-bool) ; ==> #f (next-int15) ; ==> 5 (next-int15) ; ==> 4 (next-int15) ; ==> 2 (next-int15) ; ==> 1 (next-int15) ; ==> 3 (next-int15) ; ==> 3 (random-bool) ; ==> #t (random-bool) ; ==> #t
返回的值是随机的,所以它就是我第一轮的结果。而不是命名 next-element 一个人可以简单地返回lambda,但是名称提供了它的作用信息,调试器将显示名称。例如。:
next-element
next-int15 ; ==> #<procedure:next-element>
你不需要使用 eval 在这里。它使解决方案更加复杂(和 不安全 )比需要的。此外,“循环”逻辑是不正确的,因为你没有更新位置 lst-nouns ,无论如何,每次调用该过程时都会重新定义它。另外,请参阅 链接 Sorawee分享了解原因 eval 看不到本地绑定。
lst-nouns
在Scheme中,我们尽可能避免变异状态,但对于这个过程,我认为这是合理的。诀窍是保持需要在闭包内更新的状态;这是一种方法:
(define nouns '(time year people way day man)) ; notice that `next-noun` gets bound to a `lambda` ; and that `lst-nouns` was defined outside of it ; so it's the same for all procedure invocations (define next-noun ; store list position in a closure outside lambda (let ((lst-nouns '())) ; define `next-noun` as a no-args procedure (λ () ; if list is empty, reset with shuffled original (when (null? lst-nouns) (set! lst-nouns (shuffle nouns))) ; obtain current element (let ((noun (car lst-nouns))) ; advance to next element (set! lst-nouns (cdr lst-nouns)) ; return current element noun))))
@PetSerAl在评论中提出了更为惯用的解决方案。我的猜测是你想从头开始实现这个目的 - 但是在现实生活中我们会做这样的事情,使用Racket的 发电机 :
(require racket/generator) (define next-noun (infinite-generator (for-each yield (shuffle nouns))))
无论哪种方式,它按预期工作 - 反复呼叫 next-noun 将返回所有元素 nouns 直到用完为止,此时列表将重新洗牌,迭代将重新启动:
next-noun
nouns
(next-noun) => 'day (next-noun) => 'time ...