Регистрация | Войти
Lisp — программируемый язык программирования

Функции

Функции, производящие функции

"Как определить функцию, которая бы возвращала функцию?" - типичный вопрос человека, изучавшего Scheme до Common Lisp. В Scheme вы можете сделать это так:

* (define (adder n) (lambda (x) (+ x n)))
adder
* ((adder 3) 5)
8
(define (doubler f) (lambda (x) (f x x)))
doubler
* ((doubler +) 4)
8

Конечно же, это можно сделать и в Common Lisp, но синтаксис и семантика будут немного отличаться. Определение функции, возвращающей функцию, практически такое же:

* (defun adder (n)
    (lambda (x) (+ x n))
)

adder

Мы определили функцию adder, которая производит объект типа function. Объект, который создает функция adder, например, (adder 5), - это замыкание, то есть функция, выполняемая в том лексическом окружении, в котором была создана.

Тем не менее, несмотря на то, что (adder 5) имеет тип "функция", мы не можем применить ее к аргументам так, как сделали бы это в Scheme:

* ((adder 5) 3)
Error: Illegal function call ...

И вот почему: в Common Lisp функции и переменные находятся в разных пространствах имен, и одно и то же имя может соответствовать как переменной, так и функции, в зависимости от способа ее использования.

* (boubdp 'foo)
nil
* (fboundp 'foo)
nil
* (defparameter foo 42)
foo
* foo
42
* (boundp 'foo)
t
* (fboundp 'foo)
nil
* (defun foo (x) (* x x))
foo
* (fboundp 'foo)
t
* foo
42
* (foo 3)
9
* (foo foo)
1764
* (function foo)
#<function foo>
* #'foo
#<function foo>
* (let ((+ 3))
    (+ + +)
)

6

Каждый символ в CL может иметь как минимум два слота, в которых хранится информация о нем. Один из них хранит значение, связанное с этим символом. Чтобы узнать, связано ли какое-нибудь значение с этим символом, воспользуйтесь boundp. Узнать это значение можно с помощью symbol-value.

Другой слот хранит ссылку на определение функции, которая связана с символом. Связана ли какая-нибудь функция с символом, подскажет fboundp, а получить ссылку (в глобальном окружении) на определение функции можно с помощью symbol-function.

Теперь, когда символ связан с переменной и с функцией, его использование определяется положением в коде. Если вычисляется составная форма, например, (cons x y), то ее голова, (car (cons x y)) == cons, считается функцией. В других случаях она используется как переменная.

Таким образом, в отличие от Scheme, в Common Lisp голова составной формы не может быть произвольным. Если это не символ, то это должно быть lambda-выражением: (lambda lambda-list form*). Это объясняет ошибку, вызванную вызовом ( (adder 5) 3 ). (adder 5) - это не символ и не лямбда-выражение. Но каким же образом мы можем вызвать функцию, произведенную функцией adder? Ответ таков: используйте funcall или apply:

* (funcall (adder 5) 3)
8
* (apply (adder 3) '(5))
8
* (defparameter *my-fun* (adder 3))
*my-fun*
* *my-fun*
#<closure (lambda (n))>
* (funcall *my-fun* 5)
8
* (*my-fun* 5)
Warning: this function is undefined:
 *my-fun*

В последнем примере показано, что объект, возвращаемый (adder 5), является значением переменной *my-fun*. Чтобы использовать его в car составной формы, как это делалось бы, если бы *my-fun* была бы определена с помощью defun, мы должны явно сохранить ее в слоте *my-fun*, соответствующем функции.

* (fboundp '*my-fun*)
nil
* (setf (symbol-function '*my-fun*) (adder 3))
#<closure (lambda (n))>
* (fboundp '*my-fun*)
t
* (*my-fun* 5)
8

Теперь мы можем определить doubler:

* (defun doubler (f)
    (lambda (x) (funcall f x x))
)

doubler
* (doubler #'+)
#<closure (lambda (x))>
* (doubler '+)
#<closure (lambda (x))>
* (funcall (doubler #'+) 4)
8
* (funcall (doubler '+) 4)
8
* (defparameter *my-plus* '+)
*my-plus*
* (funcall (doubler *my-plus*) 4)
8
* (defparameter *my-fun* (doubler '+))
*my-fun*
* (funcall *my-fun* 4)
8

Аргумент для funcall (а также apply) может быть либо сама функция (#'+), или символ, ссылающийся на эту функцию ('+).

Все, что было сказано выше - это сильно упрощённый, беглый обзор, в котором не были упомянуты макросы, специальные формы, самовычисляющиеся объекты, лексические окружения. Для получения более полного представления по этому вопросу, прочтите об этом в CLHS: Form Evaluation.

Каррирование

Каррирование (карринг) - связанная с функцией концепция, пришедшая из функционального программирования. После прочтения последней секции, ее можно с легкостью реализовать:

* (declaim (ftype (function (function &rest t) function) curry)
                  (inline curry)
)

nil
* (defun curry (function &rest args)
    (lambda (&rest more-args)
      (apply function (append args more-args))
)
)

curry
* (funcall (curry #'+ 3) 5)
8
* (funcall (curry #'+ 3) 6)
9
* (setf (symbol-function 'power-of-ten) (curry #'expt 10))
#<closure (lambda (&rest more args))>
* (power-of-ten 3)
1000

declaim - это указание компилятору, чтобы тот производил более эффктивный код. Это не обязательно.

@2009-2013 lisper.ru