Обозначения #'
и funcall
необходимы в Common Lisp, потому что этот язык является так называемым "Lisp-2", где данный символ может иметь два отдельных и несвязанных основных "значения", обычно перечисляемые как
- При использовании в качестве первого элемента формы это означает функцию
- При использовании в любом другом месте это означает переменную
Это приблизительные объяснения, как вы увидите в следующем примере, что «первый элемент формы» и «любое другое место» не являются правильными определениями.
Рассмотрим для примера:
![lisp-2](https://i.stack.imgur.com/WyQml.png)
вышеприведенный код печатает 144
... поначалу это может показаться удивительным, но причина в том, что одно и то же имя square
используется в двух разных значениях: функция, которая дает аргумент, возвращает результат умножения аргумента сам по себе и локальная переменная квадрат со значением 12.
Первое и третье использование имени square
означает, что это функция с именем square
, и я закрасил имя красным цветом. Второе и четвертое использование вместо этого имеют переменную с именем square
и окрашены в синий цвет.
Как Common Lisp может решить, что есть что? точка - это позиция ... после defun
в данном случае ясно, что это имя функции, как будто это имя функции в первой части (square square)
. Аналогично, как первый элемент списка внутри формы let
, это явно имя переменной, а также имя переменной во второй части (square square)
.
Это выглядит довольно психотично ... не так ли? Что ж, в сообществе Lisp действительно есть некоторые разногласия относительно того, собирается ли это двойное значение упростить или усложнить ситуацию, и это одно из главных различий между Common Lisp и Scheme.
Не вдаваясь в детали, я просто скажу, что этот явно сумасшедший выбор был сделан, чтобы сделать макросы Lisp более полезными, обеспечив достаточную гигиену, чтобы они работали без лишней сложности и лишенной выразительной силы гигиенических макросов. Конечно, это усложнение, которое усложняет объяснение языка тому, кто его изучает (и именно поэтому Scheme считается лучшим (более простым) языком для обучения), но многие эксперты считают, что это хороший выбор, который делает язык Lisp лучший инструмент для реальных проблем.
Также в человеческих языках контекст играет важную роль в любом случае, и для людей не представляет серьезной проблемы то, что иногда одно и то же слово может использоваться в разных значениях (например, в качестве существительного или глагола типа «Калифорния - это штат» Я живу в "или" Выскажи свое мнение ").
Даже в Lisp-2 вам, однако, нужно использовать функции в качестве значений, например передавать их в качестве параметров или сохранять их в структуре данных, или вам нужно использовать значения в качестве функций, например, вызывать функцию, которая была получена в качестве параметра (ваш сюжетный случай) или который был сохранен где-то. Здесь #'
и funcall
вступают в игру ...
#'foo
- это просто ярлык для (function foo)
, точно так же, как 'x
- это ярлык для (quote x)
. Эта «функция» - это специальная форма, которая с именем (в данном случае foo
) возвращает связанную функцию в виде значения, которое вы можете сохранить в переменных или передать:
(defvar *fn* #'square)
в приведенном выше коде, например, переменная *fn*
получит функцию, определенную ранее. Значением функции можно манипулировать как любым другим значением, например строкой или числом.
funcall
- это противоположность, позволяющая вызывать функцию не по имени, а по значению ...
(print (funcall *fn* 12))
приведенный выше код будет отображать 144 ... потому что функция, которая была сохранена в переменной *fn*
, теперь вызывается с передачей 12 в качестве аргумента.
Если вы знаете язык программирования "C", аналогия рассматривает (let ((p #'square))...)
как получение адреса функции square
(как с { int (*p)(int) = □ ...}
), а вместо этого (funcall p 12)
похоже на вызов функции с помощью указателя ( как с (*p)(12)
, что "C" позволяет сокращаться до p(12)
).
Чрезвычайно запутанная часть в Common Lisp состоит в том, что вы можете иметь как функцию с именем square
, так и переменную с именем square
в той же области видимости, и эта переменная не будет скрывать функцию. funcall
и function
- это два инструмента, которые вы можете использовать, когда вам нужно использовать значение переменной в качестве функции или когда вы хотите использовать функцию в качестве значения соответственно.