Смущает разница между let и let * в Scheme

Может ли кто-нибудь объяснить разницу просто? Я не думаю, что понимаю концепцию из учебников/сайтов, с которыми я консультировался.

Ответ 1

Если вы используете let, вы не можете ссылаться на другие привязки, которые появляются в том же выражении let.

Например, это не сработает:

(let ((x 10)
      (y (+ x 6))) ; error! unbound identifier: x
  y)

Но если вы используете let*, можно сослаться на предыдущие привязки, которые появляются в том же выражении let*:

(let* ((x 10)
       (y (+ x 6))) ; works fine
  y)
=> 16

Все это здесь, в документации.

Ответ 2

Let является параллельным, (вид, см. ниже) let* является последовательным. Let означает

((lambda(a b c)  ... body ...)
  a-value
  b-value
  c-value)

но let* как

((lambda(a)
    ((lambda(b)
       ((lambda(c) ... body ...)
        c-value))
     b-value))
  a-value)

и, таким образом, создает блоки вложенных областей, где выражение b-value может ссылаться на a, а выражение c-value может ссылаться как на b, так и на a. a-value относится к внешней области. Это также эквивалентно

(let ((a a-value))
  (let ((b b-value))
    (let ((c c-value))
      ... body ... )))

Существует также letrec, что позволяет использовать рекурсивные привязки, где все переменные и выражения принадлежат одной общей области и могут ссылаться друг на друга (с некоторыми оговорками, относящимися к инициализации). Это эквивалентно либо

(let ((a *undefined*) (b *undefined*) (c *undefined*))
  (set! a a-value)
  (set! b b-value)
  (set! c c-value)
  ... body ... )

(в Racket, также доступный как letrec* на схеме, так как R6RS) или

(let ((a *undefined*) (b *undefined*) (c *undefined*))
  (let ((_x_ a-value) (_y_ b-value) (_z_ c-value))   ; unique identifiers
    (set! a _x_)
    (set! b _y_)
    (set! c _z_)
    ... body ... ))

(в схеме).

update: Let фактически не оценивает свои значения-выражения параллельно, просто они все оцениваются в той же начальной среде, где появляется форма Let. Это также ясно из перевода lambda: сначала выражения значения оцениваются каждый в одной и той же внешней среде, и полученные значения собираются, и только , а затем создаются новые местоположения для каждого id и значения ставятся каждый в своем местоположении. Мы все еще можем видеть последовательность, если одно из значений-выражений мутирует хранилище (т.е. Данные, такие как список или структура), к которым обращается последующий.