Как освежить, переделать, лексические привязки на лямбду? - PullRequest
0 голосов
/ 27 января 2019

Я пытаюсь понять, как заново связать лексическое связывание или переопределить закрытие лямбды. Ожидаемое использование следующего имени - просто вызывать его столько раз, сколько нужно, без аргументов. Он должен вернуть случайное существительное из списка, но еще не возвращенное, пока список не будет исчерпан.

Вот пример игрушки, который я использую:

#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 голосов
/ 29 января 2019

Вы выдаете с 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 можно просто вернуть лямбду, но имя дает информацию о том, что он делает, и отладчик покажет имя. eg.:

next-int15 ; ==> #<procedure:next-element>
0 голосов
/ 27 января 2019

Вам вообще не нужно использовать eval здесь.Это делает решение более сложным (и небезопасным ), чем необходимо.Кроме того, логика «зацикливания» неверна, потому что вы не обновляете позицию в lst-nouns, и в любом случае она переопределяется при каждом вызове процедуры.Кроме того, посмотрите ссылку , предоставленную Сорави, чтобы понять, почему eval не может видеть локальные привязки.

В Схеме мы стараемся по возможности избегать состояния мутации, но для этой процедуры я думаю,это оправдано.Хитрость заключается в том, чтобы сохранить состояние, которое должно быть обновлено внутри замыкания;это один из способов сделать это:

(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)
=> 'day
(next-noun)
=> 'time
...
...