Перейти к содержанию

Лисп/Функции

Материал из Викиучебника — открытых книг для открытого мира

Если вы уже знакомы с каким-нибудь алголоподобным языком типа Си или Пёрла, то понятие функции в Лиспе может показаться странным, но оно значительно ближе к математическому явлению функции, чем «функции» Си, которые вернее бы назвать более процедурами. Функция в Лиспе есть однозначное отображение множества исходных данных на множество её значений. У функции может быть произвольно много аргументов, — от нуля до любого конечного числа, — но обязательно должно быть хотя бы одно значение. Обычная для многих языков префиксная запись вызова функций:

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!