Определение функций и их вычисление в Лиспе основано на лямбда-исчислении Чёрча, предлагающем для этого точный и простой формализм. Лямбда-выражение, позаимствованное Лиспом из лямбда-исчисления является важным механизмом в практическом программировании.
В лямбда-исчислении Чёрча функция записывается в следующем виде:
lambda(x1,x2,…,xn).fn
В Лиспе лямбда-выражение имеет вид:
(lambda (x1 x2 … xn) {fn})
Символ lambda означает, что мы имеем дело с определением функции. Символы xi являются формальными параметрами определения, которые именуют аргументы в описывающем вычисления теле функции {fn}. Входящий в состав формы список, образованный из параметров, называют лямбда-списком.
Телом функции является произвольная форма, значение которой может вычислить интерпретатор Лиспа, например: константа, связанный со значением символ или композиция из вызовов функций. Функцию, вычисляющую сумму квадратов двух чисел можно, например, определить следующим лямбда-выражением:
(lambda (x y) ((x * x) + (y * y))
Формальность параметров означает, что их можно заменить на любые другие символы, и это не отразится на вычислениях, определяемых функцией. Именно это и скрывается за лямбда-нотацией. С её помощью возможно различать понятия определения и вызова функции. Например, функцию list для двух аргументов можно определить любым из двух лямбда-выражений:
(lambda (x y) (nil cons x (nil cons y nil))) (lambda (кот пёс) (nil cons кот (nil cons пёс nil)))
Здесь значение вызова функции cons, являющегося телом лямбда-выражения, зависит от значений связей (другими словами, от значений переменных).
Лямбда-выражение - это определение вычислений и параметров функции в чистом виде без фактических параметров, или аргументов. Для того, чтобы применить такую функцию к некоторым аргументам, нужно в вызове функции поставить лямбда-определение на место имени функции:
(obj лямбда-выражение a1 a2 … an)
Здесь ai - формы, задающие фактические параметры, которые вычисляются как обычно. Например, действие сложения для чисел 2 и 3
>(2 + 3) 5
можно записать с использованием вызова лямбда-выражения:
>(2 ; объект (lambda (x) (this + x)) ; лямбда-определение 3) ; аргумент 5 ; результат
Следующий вызов строит список из аргументов a и b:
>(nil (lambda (x y) (nil cons x (nil cons y nil))) 'a 'b) (a b)
Такую форму вызова называют лямбда-вызовом.
Вычисление лямбда-вызова, или применение лямбда-выражения к фактическим параметрам, производится в два этапа. Сначала вычисляются значения фактических параметров и соответствующие формальные параметры связываются с полученными значениями. Этот этап называется связыванием параметров. На следующем этапе с учётом новых связей вычисляется форма, являющаяся телом лямбда-выражения, и полученное значение возвращается в качестве значения лямбда-вызова. Формальным параметрам после окончания вычисления возвращаются те связи, которые у них, возможно, были перед вычислением лямбда-вызова. Весь этот процесс называют лямбда-преобразованием.
Лямбда-вызовы можно свободно объединять между собой и другими формами. Вложенные лямбда-вызовы можно ставить как на место тела лямбда-выражения, так и на место фактических параметров. Например, в следующем вызове тело лямбда-выражения содержит вложенный лямбда-вызов:
>('внешний (lambda () ; у лямбда-вызова тело вновь лямбда-вызов ('внутренний (lambda (x) (nil list this x)) this))) (внутренний внешний)
В приведённом ниже примере лямбда-вызов является аргументом другого вызова:
>(('первый (lambda () ; лямбда-вызов у которого аргументом является новый лямбда-вызов (nil list this))) (lambda () (nil list 'второй this))) (второй (первый))
Обратите внимание, что лямбда-выражение без аргументов (фактических параметров) представляет собой лишь определение, но не форму, которую можно вычислить. Само по себе оно интерпретатором не воспринимается:
>(lambda (x y) (nil cons x (nil cons y nil))) Error:eval: Symbol lambda have no value
Символ lambda не имеет значения
Лямбда-выражение является как чисто абстрактным механизмом для определения и описания вычислений, дающим точный формализм для параметризации вычислений при помощи переменных и изображения вычислений, так и механизмом для связывания формальных и фактических параметров на время выполнения вычислений. Лямбда-выражение - это безымянная функция, которая пропадает тотчас после вычисления значения формы. Её трудно использовать снова, так как нельзя вызвать по имени, хотя ранее выражение было доступно как списочный объект. Однако используются и безымянные функции, например при передаче функции в качестве аргумента другой функции или при формировании функции в результате вычислений, другими словами, при синтезе программ.
Лямбда-выражение соответствует используемому в других языках определению процедуры или функции, а лямбда-вызов - вызову процедуры или функции. Различие состоит в том, что для программиста лямбда-выражение - это лишь механизм, и оно не содержит имени или чего-либо подобного, позволяющего сослаться на это выражение из других вызовов. Записывать вызовы функций полностью с помощью лямбда-вызовов не разумно, поскольку очень скоро выражения в вызове пришлось бы повторять, хотя разные вызовы одной функции отличаются лишь в части фактических параметров. Проблема разрешима если дать имя лямбда-выражению и использования в вызове лишь имени.
Дать имя и определить новую функцию можно с помощью функции defmethod. Её действие с абстрактной точки зрения аналогично функциям (set и другие). defmethod вызывается так:
(имя-класса defmethod имя-функции список-аргументов тело)
Что можно представить себе как сокращение записи
(имя-класса defmethod имя-функции лямбда-выражение)
из которой для удобства исключены внешние скобки лямбда-выражения и сам атом lambda. Например:
(nil defmethod list1 (lambda (x y) (nil cons x (nil cons y nil))))→
(nil defmethod list1 (x y) (nil cons x (nil cons y nil)))
defmethod соединяет символ с лямбда-выражением, и символ начинает представлять (именовать) определённые этим лямбда-выражением вычисления. Значением этой формы является лямбда-выражение новой функции:
>(nil defmethod list1 (x y) (nil cons x (nil cons y nil))) ; определение (lambda (x y) (nil cons x (nil cons y nil))) >(nil list1 'a 'b) ; вызов (a b)
Приведём ещё несколько примеров:
>(nil defmethod lambdap (выражение) ; проверяет на лямбда-выражение (nil if (nil consp выражение) (nil eq (выражение first) 'lambda))) (lambda (выражение) (nil if (nil consp выражение) (nil eq (выражение first) 'lambda))) >(nil lambdap '(nil list1 'a 'b)) false >('number defmethod проценты (часть) ; вычисляет % части ((часть / this) * 100)) (lambda (часть) ((часть / this) * 100)) >(25 проценты 4) 16
Ранее был рассмотрен предикат boundp, проверяющий наличие у символа значения. Соответственно функция getmethod возвращает определение метода, которую можно воспринимать как в логическом смысле, так и для взятия его определения.
>(nil getmethod 'list1) (lambda (x y) (nil cons x (nil cons y nil)))
Поскольку определение метода задаётся списком, а он всегда доступен программе, то можно исследовать работу методов и даже время от времени модифицировать её, изменяя определения (например, в задачах обучения). В традиционных языках программирования, предполагающих трансляцию, это было бы невозможно. Символ может одновременно именовать некоторое значение и метод, и эти возможности не мешают друг другу. Позиция символа в выражении определяет его интерпретацию:
>(nil setq list1 'a) nil >(nil list1 list1 'b) (a b)
Символы могут дополнительно к значению и определению обладать в более общем виде и другими свойствами, определёнными пользователями, т.е. обладать списком свойств. Определение метода и значение переменных являются лишь двумя различными свойствами переменных, которые программист при необходимости может при помощи списка свойств дополнить или изменить.
Рассмотренной ранее defmethod-формы вполне достаточно для изучения Лиспа. Однако defmethod-форма содержит, кроме этого, механизм ключевых слов, с помощью которых аргументы вызова метода можно при желании трактовать по-разному.
С помощью ключевого слова в лямбда-списке можно выделить параметр, связываемый с хвостом списка аргументов изменяющейся длины.
Ключевые слова начинаются со знака &, и их записывают перед соответствующими параметрами в лямбда-списке.
&rest | Переменное количество аргументов |
Аргумент, указанный после ключевого слова &rest, связывается со списком несвязанных аргументов, указанных в вызове. Таким функциям можно передавать переменное количество аргументов.
Например:
>(nil defmethod fn (x &rest y) (nil list x y)) (lambda (x &rest y) (nil list x y)) >(nil fn 'a) (a nil) >(nil fn 'a 'b 'c) (a (b c))
В данной реализации нет механизма, с помощью которого можно было бы обозначать параметры, не требующие вычисления. Блокирующие вычисление аргумента метода и формы, такие как ', setq и defmethod, определяются через механизм макросов или макрооператоров.