我正在尝试查看如何重新绑定词汇绑定或重新定义 关闭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"
这让我感到困惑,因为应该为任何第一名词绑定 时间(eval func-syn)运行。发生了什么事?
答案 0 :(得分:4)
您根本不需要在这里使用eval
。这使得解决方案比所需的更加复杂(和insecure)。此外,“循环”逻辑是不正确的,因为您没有更新lst-nouns
中的位置,并且无论如何每次调用该过程都会重新定义它。另外,请参阅Sorawee共享的link,以了解为什么eval
无法看到本地绑定。
在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
...
答案 1 :(得分:0)
您的问题与eval
有关。 eval
没有所谓的词法环境,而是最多具有顶级绑定。例如。
(define x 12)
(let ((x 10))
(eval '(+ x x))) ; ==> 24
eval
几乎总是错误的解决方案,通常可以用闭包代替并直接调用或用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-int15 ; ==> #<procedure:next-element>