Лисп/Функции
Материал из Викиучебника
Если вы уже знакомы с каким-нибудь алголоподобным языком типа Си или Пёрла, то понятие функции в Лиспе может показаться странным, но оно значительно ближе к математическому явлению функции, чем «функции» Си, которые вернее бы назвать более процедурами. Функция в Лиспе есть однозначное отображение множества исходных данных на множество её значений. У функции может быть произвольно много аргументов, — от нуля до бесконечности любого конечного числа, — но обязательно должно быть хотя бы одно значение. Обычная для многих языков префиксная запись вызова функций:
plus(2,2)
несколько видоизменяется в силу отсутствия синтаксиса: идентификатор ("имя" функции, спецоператора или макровызова) становится головным элементом… да, списка:
(plus 2 2)
В чистом функциональном лиспе (являющемся строгим подмножеством любого из диалектов) функции не должны обладать побочным эффектом, то есть изменять значения переменных с нелокальным состоянием. В противном случае, при нескольких вызовах одной функции могут быть получены разные значения, что запутывает любую сложную программную систему, и чего стремится избежать функциональное программирование.
Определение функции основано на лямда-исчислении. Исходный вариант записи лямда-выражения, предложенный его автором Чёрчем, выглядит как λ(x1,x2...)fn. Лисп-запись выглядит так: (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!