Так же как выражение, являющееся вызовом функции, без предшествующего апострофа представляет значение выражения, а не само выражение, так и атомы могут использоваться для обозначения каких-нибудь значений. Уже обращено внимание на то, что перед лисповскими константами (числами и символами true, false и nil) не надо ставить апостроф. Как константы они обозначают самих себя. Если ввести константу, то интерпретатор в качестве результата выдаст саму эту константу:
>true ; значением true является он сам true >'true ; апостроф не нужен true >nil nil >3.14 3.14
Символы можно использовать как переменные. В этом случае они могут обозначать некоторые выражения. У символов изначально нет какого-нибудь значения, как у констант. Если, например, введём символ функции, то получим сообщение об ошибке:
>функции Error: Symbol функции have no value
Интерпретатор здесь не может вычислить значение символа, поскольку его у него нет.
При помощи функции set символу можно присвоить или связать с ним некоторое значение. Если, например, мы хотим, чтобы символ функции обозначал базовые функции Лиспа, то введём:
>('функции set '(first rest cons atom eq)) (first rest cons atom eq)
Теперь между символом функции и значением (first rest cons atom eq) образована связь, которая действительна до окончания работы, если, конечно, этому имени функцией set не будет присвоено новое значение. После присваивания интерпретатор уже может вычислить значение символа функции:
>функции (first rest cons atom eq)
Обратите внимание, что set вычисляет оба аргумента. Если перед первым аргументом нет апострофа, то с помощью функции set можно присвоить значение имени, которое получается путём вычисления. Например, вызов
>((функции first) set '(взбрести в голову)) (взбрести в голову)
присваивает переменной first выражение (взбрести в голову), так как вызов (функции first) возвращает в качестве значения символ first, который и используется как фактический аргумент вызова функции set:
>(функции first) ; первый аргумент предыдущего вызова first >first ; присвоенное функцией set значение (взбрести в голову) >функции (first rest cons atom eq)
Наряду с функцией set связать символ с его значением можно с помощью функции setq. Эта функция отличается от set тем, что она вычисляет только свой второй аргумент. Об автоматическом блокировании вычисления первого аргумента напоминает буква q в имени функции. Например:
>(nil setq функции '(first rest cons atom eq)) nil
При использовании функции setq отпадает надобность в знаке апострофа перед первым аргументом.
Проверить, связан ли атом, можно с помощью предиката boundp, который истинен, когда атом имеет какое-нибудь значение:
>('беззначения boundp) false >('функции boundp) true >('true boundp) ; константа всегда связана true
Функции set и setq отличаются от других рассмотренных функций тем, что помимо того, что они имеют значение, они обладают и побочным эффектом. Эффект функции состоит в образовании связи между символом и его значением, а значением функции является связываемое значение. Символ остаётся связанным с определённым значением до тех пор, пока это значение не изменят.
В Лиспе все действия возвращают некоторое значение. Значение имеется даже у таких действий, основное предназначение которых заключается в осуществлении побочного эффекта, таких, например, как связывание символа или вывод результатов на печать. Функции, обладающие побочным эффектом, в Лиспе называют псевдофункциями. Будем всё же как для функций, так и для псевдофункций использовать понятие функции, если только нет особой надобности подчеркнуть наличие побочного эффекта.
Вызов псевдофункции, например оператор передачи управления (а это тоже вызов), с точки зрения использования его значения может стоять на месте аргумента другой функции. (В языках программирования, основанных на операторном подходе, это обычно невозможно.)
>(nil list ('a set 3) 4) (3 4) >a 3 >(nil list (('b set 3) + 4) b) ; аргументы вычисляются параллельно Error: Symbol b have no value
В практическом программировании псевдофункции полезны и часто необходимы, хотя в теории, чистом функциональном программировании, они не нужны.
Интерпретатор Лиспа называется eval, и его можно так же, как и другие функции вызывать из программы. При обычном программировании вызывать интерпретатор не надо, так как этот вызов неявно присутствует в диалоге программиста с Лисп-системой.
Лишний вызов интерпретатора может, например, снять эффект блокировки вычисления или позволяет найти значение выражения, т.е. осуществить двойное вычисление:
>'(2 + 3) (2 + 3) >(nil eval '(2 + 3)) 5
Здесь интерпретатор вычисляет значение первого выражения '(2 + 3), получая в результате (2 + 3). Далее вызов eval позволяет вновь запустить интерпретатор, и результатом будет значение выражения (2 + 3), т.е. 5. Исходное выражение обрабатывается в два этапа. Приведём ещё несколько примеров:
>(nil setq x '(a b c)) nil >'x x >(nil eval 'x) (a b c) >(nil eval x) ; значением x является (a b c) Error: have no function link of b for 3 >(nil eval ''(a b c)) (a b c) >'(nil eval x) (nil eval x)
' и eval действуют во взаимно противоположных направлениях и аннулируют эффект друг друга.
eval - это универсальная функция Лиспа, которая может вычислить любое правильно составленное выражение. eval определяет семантику форм, т.е. определяет, какие символы и формы совместно с чем и что означают и какие выражения имеют значение.
Семантику Лиспа можно довольно компактно и метко определить на самом Лиспе. Большая часть системных программ в Лисп-системах написана на Лиспе, и пользователь может легко изменить систему в зависимости от своих потребностей.
Диалог с интерпретатором Лиспа на самом верхнем, командном уровне, можно описать простым циклом:
(cout write '>) ; вывод приглашения (nil setq e (cin read)) ; ввод выражения (nil setq v (nil eval e)) ; вычисление его значения (cout print v) ; вывод результата (cout write '>) ; повторение цикла …
Интерпретатор Лиспа отвечает на заданный ему вопрос и ждёт новое значение.