Подтвердить что ты не робот

Разница между define, let и set!

Хорошо, это довольно простой вопрос: я следую за SICP-видео, и я немного смущен различиями между define, let и set!.

1) Согласно Суссману в видео, define разрешено присоединять значение к avariable только один раз (кроме случаев, когда в REPL), в частности два разрешенных в строке не допускаются. Тем не менее, Guile успешно запускает этот код

(define a 1)
(define a 2)
(write a)

и выводит 2, как и ожидалось. Это немного сложнее, потому что если я попытаюсь сделать это (EDIT: после указанных выше определений)

(define a (1+ a))

Я получаю сообщение об ошибке, а

(set! a (1+ a))

разрешено. Тем не менее, я не думаю, что это единственное различие между set! и define: чего я не хватает?

2) Разница между define и let еще более заводит меня в голову. Я знаю теоретически let используется для привязки переменных в локальной области. Тем не менее, мне кажется, что это работает с define, например, я могу заменить

(define (f x)
    (let ((a 1))
        (+ a x)))

с

(define (g x)
    (define a 1)
    (+ a x))

и f и g работают одинаково: в частности переменная a также несвязана вне g.

Единственный способ, который я вижу в этом полезном, - это то, что let может иметь более короткую область видимости всей функции. Тем не менее мне кажется, что всегда можно добавить анонимную функцию для создания необходимой области и сразу вызвать ее, как и в javascript. Итак, каково реальное преимущество let?

4b9b3361

Ответ 1

Вы имеете в виду (+ 1 a) вместо (1+ a)? Последнее не является синтаксически допустимым.

Сфера переменных, определяемая символом let, привязана к последнему, поэтому

(define (f x)
  (let ((a 1))
    (+ a x)))

является синтаксически возможным, а

(define (f x)
  (let ((a 1)))
  (+ a x))

нет.

Все переменные должны быть define d в начале функции, поэтому возможен следующий код:

(define (g x)
  (define a 1)
  (+ a x))

в то время как этот код будет генерировать ошибку:

(define (g x)
  (define a 1)
  (display (+ a x))
  (define b 2)
  (+ a x))

потому что первое выражение после определения означает, что других определений нет.

set! не определяет переменную, а используется для назначения переменной нового значения. Поэтому эти определения бессмысленны:

(define (f x)
  (set! ((a 1))
    (+ a x)))

(define (g x)
  (set! a 1)
  (+ a x))

Допустимое использование для set! выглядит следующим образом:

(define x 12)
> (set! x (add1 x))
> x
13

Хотя это обескураживает, поскольку Scheme является функциональным языком.

Ответ 2

Ваша путаница разумна: "пусть" и "определять" создают новые привязки. Одно из преимуществ "пусть" заключается в том, что его значение необычайно четко определено; нет абсолютно никаких разногласий между различными системами Схемы (включая Ракету) о том, что означает "пустой".

Форма "определить" - это другой чайник рыбы. В отличие от "let", он не окружает тело (область, где привязка действительна) с круглыми скобками. Кроме того, это может означать разные вещи на верхнем уровне и внутри. Различные системы схем имеют совершенно разные значения для "определения". Фактически, Racket недавно изменил значение "define", добавив новые контексты, в которых это может произойти.

С другой стороны, людям нравится "определять"; он имеет меньше отступов, и он обычно имеет уровень "делать-то-я-средний", позволяющий естественные определения рекурсивных и взаиморекурсивных процедур. Фактически, я был укушен этим только на днях:).

Наконец, 'set!'; как "пусть" , "установить!" довольно просто: он мутирует существующую привязку.

FWIW, один из способов понять эти области в DrRacket (если вы его используете) - использовать кнопку "Проверить синтаксис", а затем навести курсор на различные идентификаторы, чтобы увидеть, где они связаны.

Ответ 3

Джон Клементс ответил хорошо. В некоторых случаях вы можете увидеть, что означает define в каждой версии Схемы, что может помочь вам понять, что происходит.

Например, в Chez Scheme 8.0 (у которого есть свои собственные трюки define, esp. wrt R6RS!):

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(begin
  (set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
  (#2%void))

Вы видите, что определение верхнего уровня становится set! (хотя только расширение define в некоторых случаях изменит ситуацию!), но внутреннее определение (т.е. define внутри другого блока) становится a letrec*. Различные схемы расширят это выражение на разные вещи.

MzScheme v4.2.4:

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(define-values
 (g)
 (lambda (x)
   (letrec-values (((a) '1)) (#%app + a x))))

Ответ 4

Вы можете использовать define более одного раза, но это не idiomatic: define подразумевает, что вы добавляете определение в среда и set! подразумевает, что вы изменяете некоторую переменную.

Я не уверен в Guile и почему он позволил бы (set! a (+1 a)), но если a еще не определено, что не должно работать. Обычно можно использовать define, чтобы ввести новую переменную и только мутировать ее с помощью set! позже.

Вы можете использовать приложение анонимной функции вместо let, в факт, что обычно именно то, что let расширяется, почти всегда макрос. Они эквивалентны:

(let ((a 1) (b 2))
  (+ a b))

((lambda (a b)
   (+ a b))
 1 2)

Причина, по которой вы используете let, состоит в том, что она понятна: имена переменных находятся рядом со значениями.

В случае внутренних определений я не уверен, что Ясир верный. По крайней мере, на моей машине, запуск Racket в режиме R5RS и в регулярный режим допускает, чтобы внутренние определения отображались в середине определение функции, но я не уверен, что говорит стандарт. В любой случае, намного позже в SICP, сложность, которую внутренняя определяет позу, подробно обсуждается. В главе 4, как реализовать взаимно рекурсивный внутренние определения изучаются и что это означает для реализации метациркулярного переводчика.

Так держись! SICP - блестящая книга, и видео-лекции замечательны.