Разница между 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
?
Ответы
Ответ 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 - блестящая книга, и видео-лекции замечательны.