Разница между LET и SETQ?
Я программирую на Ubuntu, используя GCL. Из документации по Common Lisp из разных источников я понимаю, что let
создает локальные переменные, а setq
устанавливает значения существующих переменных. В следующих случаях мне нужно создать две переменные и суммировать их значения.
Использование setq
(defun add_using_setq ()
(setq a 3) ; a never existed before , but still I'm able to assign value, what is its scope?
(setq b 4) ; b never existed before, but still I'm able to assign value, what is its scope?
(+ a b))
Использование let
(defun add_using_let ( )
(let ((x 3) (y 4)) ; creating variables x and y
(+ x y)))
В обоих случаях я, кажется, добился того же результата; в чем разница между использованием setq
и let
здесь? Почему я не могу использовать setq
(так как это синтаксически легко) во всех местах, где мне нужно использовать let
?
Ответы
Ответ 1
setq
присваивает значение переменной, тогда как let
вводит новые переменные/привязки. Например, посмотрите, что происходит в
(let ((x 3))
(print x) ; a
(let ((x 89))
(print x) ; b
(setq x 73)
(print x)) ; c
(print x)) ; d
3 ; a
89 ; b
73 ; c
3 ; d
Внешний let
создает локальную переменную x
, а внутренний let
создает другую локальную переменную, затеняющую внутреннюю. Обратите внимание, что использование let
для теневой переменной не влияет на значение тени; x
в строке d
есть x
, введенный внешним let
, и его значение не изменилось. setq
влияет только на переменную, с которой она вызывается. В этом примере показано, что setq
используется с локальными переменными, но также может быть со специальными переменными (что означает, динамически ограничено и обычно определяется с помощью defparameter
или defvar
:
CL-USER> (defparameter *foo* 34)
*FOO*
CL-USER> (setq *foo* 93)
93
CL-USER> *foo*
93
Обратите внимание, что setq
не создает (переносимо) создание переменных, тогда как let
, defvar
, defparameter
, & c. делать. Поведение setq
при вызове с аргументом, который не является переменной (пока), не определен, и это зависит от реализации, чтобы решить, что делать. Например, SBCL громко жалуется:
CL-USER> (setq new-x 89)
; in: SETQ NEW-X
; (SETQ NEW-X 89)
;
; caught WARNING:
; undefined variable: NEW-X
;
; compilation unit finished
; Undefined variable:
; NEW-X
; caught 1 WARNING condition
89
Конечно, лучшие способы лучше понять эти понятия - читать и писать больше Lisp кода (который приходит со временем) и читать записи в HyperSpec и следовать перекрестным ссылкам, особенно глоссарий записей. Например, краткие описания из HyperSpec для setq
и let
включают:
-
setq
Назначает значения переменные.
-
let
let и let * создайте новую переменную привязки и выполните серию форм, которые используют эти привязки.
Вы можете больше узнать о переменных и привязках. let
и let*
также имеют особое поведение с динамическими переменными и объявлениями special
(но вам, вероятно, не понадобится некоторое время об этом знать), а в некоторых случаях (вам, вероятно, не понадобится знать некоторое время), когда переменная на самом деле не является переменной, setq
фактически эквивалентна setf
. HyperSpec имеет более подробную информацию.
Есть несколько не совсем повторяющихся вопросов о переполнении стека, которые могут, тем не менее, помочь в понимании использования различных операторов определения и назначения переменных, доступных в Common Lisp:
Ответ 2
Давайте почти всегда будем связывать переменные внутри определения функции - за исключением редкого случая, когда вы хотите, чтобы значение было доступно для других функций в той же области.
Мне нравится описание в руководстве emacs lisp:
let используется для привязки или привязки символа к значению таким образом, что интерпретатор lisp не будет путать переменную с переменной с тем же именем, которая не является частью функции.
Чтобы понять, почему нужна специальная форма, рассмотрите ситуацию, в которой вы владеете домом, который вы обычно называете the house', as in the sentence, "The house needs painting." If you are visiting a friend and your host refers to
домом ", он, вероятно, будет ссылаться на свой дом, а не ваш, то есть, в другой дом.
Если ваш друг ссылается на свой дом, и вы думаете, что он имеет в виду ваш дом, вы можете быть в замешательстве. То же самое может произойти в lisp, если переменная, которая используется внутри одной функции, имеет то же имя, что и переменная, которая используется внутри другой функции, и эти два не предназначены для обозначения одного и того же значения. Специальная форма let предотвращает такое замешательство.
- http://www.gnu.org/software/emacs/manual/html_node/eintr/let.html
Ответ 3
(setq x y)
назначает новое значение y
переменной, обозначенной символом x
, опционально определяя новую переменную уровня пакета 1. Это означает, что после того, как вы вызвали add_using_setq
, в текущем пакете у вас будет две новые переменные уровня пакета.
(add_using_setq)
(format t "~&~s, ~s" a b)
напечатает 3 4
- маловероятный желаемый результат.
В отличие от этого, когда вы используете let
, вы назначаете новые значения только переменным, обозначенным символами, для продолжительности функции, поэтому этот код приведет к ошибке:
(add_using_let)
(format t "~&~s, ~s" a b)
Подумайте о let
как эквиваленте следующего кода:
(defun add-using-lambda ()
(funcall (lambda (a b) (+ a b)) 3 4))
В стороне, вы действительно хотите заглянуть в код, написанный другими программистами, чтобы получить представление о том, как назвать или форматировать вещи. Помимо традиционного, он также имеет некоторые типографские свойства, которые вы действительно не хотите потерять.
1 Это поведение является нестандартным, но это то, что происходит во многих популярных реализациях. Независимо от того, что он достаточно предсказуем, он считается плохой практикой по другим причинам, в основном все те же проблемы, которые препятствовали бы вам использовать глобальные переменные.
Ответ 4
вы можете получить значение символа вне области действия, пока Lisp все еще работает. (он присваивает значение символу)
вы не можете получить значение символа, определенного с помощью LET после завершения Lisp оценки формы. (привязывает значение к символу и создает новое привязку к символу)
рассмотрим пример ниже:
;; with setq
CL-USER> (setq a 10)
CL-USER> a
10
;; with let
CL-USER> (let ((b 20))
(print b))
CL-USER> 20
CL-USER> b ; it will fail
; Evaluation aborted on #<UNBOUND-VARIABLE B {1003AC1563}>.
CL-USER>