Лисп/Функции
Если вы уже знакомы с каким-нибудь алголоподобным языком типа Си или Пёрла, то понятие функции в Лиспе может показаться странным, но оно значительно ближе к математическому явлению функции, чем «функции» Си, которые вернее бы назвать более процедурами. Функция в Лиспе есть однозначное отображение множества исходных данных на множество её значений. У функции может быть произвольно много аргументов, — от нуля до любого конечного числа, — но обязательно должно быть хотя бы одно значение. Обычная для многих языков префиксная запись вызова функций:
plus(2,2)
несколько видоизменяется в силу отсутствия синтаксиса: идентификатор («имя» функции, спецоператора или макровызова) становится головным элементом… да, списка:
(plus 2 2)
В чистом функциональном лиспе (являющемся строгим подмножеством любого из диалектов) функции не должны обладать побочным эффектом, то есть изменять значения переменных с нелокальным состоянием. В противном случае, при нескольких вызовах одной функции могут быть получены разные значения, что запутывает любую сложную программную систему, и чего стремится избежать функциональное программирование.
Определение функции основано на лямбда-исчислении. Исходный вариант записи лямбда-выражения, предложенный его автором Чёрчем, выглядит как
. Лисп-запись выглядит так: (lambda (x1 x2 ...) fn)
. x1, x2 … — свободные параметры, и, так, могут быть безболезненно заменены на другие без изменении значения лямбда-выражения.
Например, сумму квадратов на языке лямбда-выражений можно определить как (lambda (x y) (+ (* x x) (* y y)))
. Вызов лямбда-функции с конкретными значениями ((lambda (x y) (+ (* x x) (* y y))) 2 3) => (+ (* 2 2) (* 3 3)) => 13
может быть сделан единожды, при желании вызвать такую функцию еще раз необходимо повторить запись еще раз. Это неудобно. Было бы удобнее написать нечто типа (сумма-квадратов 2 3). Это возможно, если вы предварительно определите функцию сумма-квадратов.
(defun сумма-квадратов (x y)
((lambda (a b) (+ (* a a) (* b b)))
x y))
…но не пугайтесь: для удобства эта запись может быть сокращена до
(defun сумма-квадратов (x y)
(+ (* x x) (* y y)))
Одно из преимуществ столь непривычных особенностей языка Лисп в том, что вы можете написать нечто типа
((lambda (x)
((lambda (y)
(list x y))
'second))
'first)
Здесь лямбда-выражение является телом другого лямбда-выражения. Оно так же может быть одним из его аргументов.
Посмотреть ранее заданное определение функции можно с помощью функции symbol-function
:
>> (symbol-function 'сумма-квадратов)
<< #<function сумма-квадратов (x y) (declare (system::in-defun сумма-квадратов))
(block сумма-квадратов (+ (* x x) (* y y)))>
Штрих перед названием функции поставлен неслучайно. В Лиспе любое имя соответствует некоторому символу. Имя функции — не исключение, то есть значение символа «сумма-квадратов
» — не определение функции; оно неопределено, пока не будет присвоено вручную (например, через setq
.) Штрих — это сокращенная запись спецоператора quote
, сообщающая интерпретатору, что нужно вывести имя данного символа без вычисления его значения.
>> (symbol-function сумма-квадратов) ; это выдаст ошибку: переменной сумма-квадратов не присвоено значение
>> (symbol-function (quote сумма-квадратов)) ; это правильно, но долго
>> (symbol-function 'сумма-квадратов) ; так лучше всего
Если все аргументы лямбда-выражения не должны вычисляться, то удобно использовать nlambda
вместо lambda
.
quote
’е противопоставлен eval
, принудительно вычисляющий значение выражения.
>> (quote сумма-квадратов) => сумма-квадратов
>> (eval (quote сумма-квадратов)) => ошибка!
>> (quote (eval (quote сумма-квадратов))) => (eval 'sum)
В последнем случае квотируется не только сумма-квадратов
, но и сам eval!