如何在lambda上刷新,重制词法绑定?

时间:2019-01-27 03:57:57

标签: binding scheme lisp racket

我正在尝试查看如何重新绑定词汇绑定或重新定义 关闭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)运行。发生了什么事?

2 个答案:

答案 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>