我正在尝试了解如何重新绑定(bind)词法绑定(bind),或者重新定义 lambda 的闭包。 next-noun 的预期用法只是在不带参数的情况下根据需要多次调用它。它应该从列表中返回一个随机名词,但在列表耗尽之前尚未返回。
这是我正在使用的玩具示例:
#lang racket
(define nouns `(time
year
people
way
day
man))
(define (next-noun)
(let* ([lst-nouns (shuffle nouns)]
[func-syn
`(λ ()
(let* ([n (car lst-nouns)]
[lst-nouns (if (null? (cdr lst-nouns))
(shuffle nouns)
(cdr lst-nouns))])
(set! next-noun (eval func-syn))
n))])
((eval func-syn))))
当尝试运行它时,我收到此错误:
main.rkt>
main.rkt> (next-noun)
; lst-nouns: undefined;
; cannot reference an identifier before its definition
; in module: "/home/joel/projects/racket/ad_lib/main.rkt"
这让我很困惑,因为 lst 名词 any 应该有一个绑定(bind) time (eval func-syn) 运行。这是怎么回事?
最佳答案
您根本不需要在这里使用eval
。它使解决方案比所需的更加复杂(和 insecure )。此外,“循环”逻辑是不正确的,因为您没有更新 lst-nouns 中的位置,而且每次调用过程时它都会被重新定义。另请参阅link由 Sorawee 分享,以了解为什么 eval
无法看到本地绑定(bind)。
在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 的 generators 来做类似的事情。 :
(require racket/generator)
(define next-noun
(infinite-generator
(for-each yield (shuffle nouns))))
无论哪种方式,它都会按预期工作 - 重复调用 next-noun
将返回 nouns
中的所有元素,直到耗尽,此时列表将重新洗牌并进行迭代将重新启动:
(next-noun)
=> 'day
(next-noun)
=> 'time
...
关于binding - 如何刷新、重制 lambda 上的词法绑定(bind)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54384930/