Введение

Особенность языка программирования

В информатике язык программирования считается обладающим функциями первого класса, если он рассматривает функции как полноправных граждан. Это означает, что язык поддерживает передачу функций в качестве аргументов другим функциям, возврат их в качестве значений из других функций и присваивание их переменным или хранение в структурах данных. Некоторые теоретики языков программирования также требуют поддержки анонимных функций (функциональных литералов). В языках с функциями первого класса имена функций не имеют особого статуса; они рассматриваются как обычные переменные с типом функции. Термин был введен Кристофером Стречи в контексте "функций как полноправных граждан" в середине 1960-х годов. Функции первого класса необходимы для функционального стиля программирования, в котором использование функций высшего порядка является стандартной практикой. Простым примером функции высшего порядка является функция `map`, которая принимает в качестве аргументов функцию и список и возвращает список, сформированный путем применения функции к каждому элементу списка. Для поддержки функции `map` язык должен поддерживать передачу функции в качестве аргумента. Существуют определенные трудности при передаче функций в качестве аргументов или возврате их в качестве результатов, особенно при наличии нелокальных переменных, возникающих во вложенных и анонимных функциях. Исторически эти проблемы назывались проблемами funarg, от "аргумента функции". В ранних императивных языках этих проблем избегали либо не поддерживая функции в качестве типов возвращаемых значений (например, ALGOL 60, Pascal), либо исключая вложенные функции и, следовательно, нелокальные переменные (например, C). Ранний функциональный язык Lisp использовал подход динамической области видимости, где нелокальные переменные ссылаются на ближайшее определение этой переменной в точке выполнения функции, а не в точке ее определения. Надлежащая поддержка функций первого класса с лексической областью видимости была введена в Scheme и требует обработки ссылок на функции как замыканий, а не простых указателей на функции.

Функции высшего порядка: возвращающие функции как результаты

Возвращая функцию, мы на самом деле возвращаем ее замыкание. В примере на C любые локальные переменные, захваченные замыканием, выйдут из области видимости, как только мы вернемся из функции, создающей это замыкание. Попытка использовать замыкание позднее приведет к неопределенному поведению и, возможно, к повреждению стека. Эта проблема известна как проблема "восходящих аргументов функции" (upward funarg problem).

Равенство функций

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

Экстенсиональное равенство. Две функции f и g считаются экстенсионально равными, если они выдают одинаковые результаты для всех входных данных (∀x. f(x) = g(x)). Согласно этому определению равенства, например, любые две реализации стабильного алгоритма сортировки, такие как сортировка вставками и сортировка слиянием, будут считаться равными. Определение экстенсионального равенства в общем случае неразрешимо, и даже для функций с конечными областями определения часто является вычислительно сложной задачей. По этой причине ни один язык программирования не реализует равенство функций как экстенсиональное равенство.

Интенсиональное равенство. При интенсиональном равенстве две функции f и g считаются равными, если они имеют одинаковую "внутреннюю структуру". Этот вид равенства может быть реализован в интерпретируемых языках путем сравнения исходного кода тел функций (например, в Interpreted Lisp 1.5) или объектного кода в компилируемых языках. Интенсиональное равенство подразумевает экстенсиональное равенство (при условии, что функции детерминированы и не имеют скрытых входных данных, таких как счетчик программы или изменяемая глобальная переменная).

Ссылочное равенство. Учитывая непрактичность реализации экстенсионального и интенсионального равенства, большинство языков, поддерживающих проверку функций на равенство, используют ссылочное равенство. Всем функциям или замыканиям присваивается уникальный идентификатор (обычно адрес тела функции или замыкания), и равенство определяется на основе равенства идентификаторов. Два отдельно определенных, но в остальном идентичных определения функции будут считаться неравными. Ссылочное равенство подразумевает интенсиональное и экстенсиональное равенство. Ссылочное равенство нарушает референциальную прозрачность и поэтому не поддерживается в чистых языках, таких как Haskell.

Теория типов

В теории типов тип функций, принимающих значения типа A и возвращающих значения типа B, может быть записан как A → B или BA. В рамках соответствия Карри — Ховарда типы функций связаны с логической импликацией; лямбда-абстракция соответствует разряду гипотетических предпосылок, а применение функции — правилу вывода modus ponens. Помимо обычного случая программирования функций, теория типов также использует функции первого класса для моделирования ассоциативных массивов и подобных структур данных. В категорно-теоретических подходах к программированию наличие функций первого класса соответствует предположению о замкнутой категории. Например, просто типизированное лямбда-исчисление соответствует внутреннему языку декартовых замкнутых категорий.