Введение
Особенность языка программирования
In computer science, a programming language is said to have first class functions if it treats functions as first class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures. Some programming language theorists require support for anonymous functions (function literals) as well. In languages with first class functions, the names of functions do not have any special status; they are treated like ordinary variables with a function type. The term was coined by Christopher Strachey in the context of "functions as first class citizens" in the mid 1960s. First class functions are a necessity for the functional programming style, in which the use of higher order functions is a standard practice. A simple example of a higher ordered function is the map function, which takes, as its arguments, a function and a list, and returns the list formed by applying the function to each member of the list. For a language to support map, it must support passing a function as an argument. There are certain implementation difficulties in passing functions as arguments or returning them as results, especially in the presence of non local variables introduced in nested and anonymous functions. Historically, these were termed the funarg problems, the name coming from "function argument". In early imperative languages these problems were avoided by either not supporting functions as result types (e. g. ALGOL 60, Pascal) or omitting nested functions and thus non local variables (e. g. C). The early functional language Lisp took the approach of dynamic scoping, where non local variables refer to the closest definition of that variable at the point where the function is executed, instead of where it was defined. Proper support for lexically scoped first class functions was introduced in Scheme and requires handling references to functions as closures instead of bare function pointers,
В информатике язык программирования считается обладающим функциями первого класса, если он рассматривает функции как полноправных граждан. Это означает, что язык поддерживает передачу функций в качестве аргументов другим функциям, возврат их в качестве значений из других функций и присваивание их переменным или хранение в структурах данных. Некоторые теоретики языков программирования также требуют поддержки анонимных функций (функциональных литералов). В языках с функциями первого класса имена функций не имеют особого статуса; они рассматриваются как обычные переменные с типом функции. Термин был введен Кристофером Стречи в контексте "функций как полноправных граждан" в середине 1960-х годов. Функции первого класса необходимы для функционального стиля программирования, в котором использование функций высшего порядка является стандартной практикой. Простым примером функции высшего порядка является функция `map`, которая принимает в качестве аргументов функцию и список и возвращает список, сформированный путем применения функции к каждому элементу списка. Для поддержки функции `map` язык должен поддерживать передачу функции в качестве аргумента. Существуют определенные трудности при передаче функций в качестве аргументов или возврате их в качестве результатов, особенно при наличии нелокальных переменных, возникающих во вложенных и анонимных функциях. Исторически эти проблемы назывались проблемами funarg, от "аргумента функции". В ранних императивных языках этих проблем избегали либо не поддерживая функции в качестве типов возвращаемых значений (например, ALGOL 60, Pascal), либо исключая вложенные функции и, следовательно, нелокальные переменные (например, C). Ранний функциональный язык Lisp использовал подход динамической области видимости, где нелокальные переменные ссылаются на ближайшее определение этой переменной в точке выполнения функции, а не в точке ее определения. Надлежащая поддержка функций первого класса с лексической областью видимости была введена в Scheme и требует обработки ссылок на функции как замыканий, а не простых указателей на функции.
In computer science, a programming language is said to have first class functions if it treats functions as first class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures. Some programming language theorists require support for anonymous functions (function literals) as well. In languages with first class functions, the names of functions do not have any special status; they are treated like ordinary variables with a function type. The term was coined by Christopher Strachey in the context of "functions as first class citizens" in the mid 1960s. First class functions are a necessity for the functional programming style, in which the use of higher order functions is a standard practice. A simple example of a higher ordered function is the map function, which takes, as its arguments, a function and a list, and returns the list formed by applying the function to each member of the list. For a language to support map, it must support passing a function as an argument. There are certain implementation difficulties in passing functions as arguments or returning them as results, especially in the presence of non local variables introduced in nested and anonymous functions. Historically, these were termed the funarg problems, the name coming from "function argument". In early imperative languages these problems were avoided by either not supporting functions as result types (e. g. ALGOL 60, Pascal) or omitting nested functions and thus non local variables (e. g. C). The early functional language Lisp took the approach of dynamic scoping, where non local variables refer to the closest definition of that variable at the point where the function is executed, instead of where it was defined. Proper support for lexically scoped first class functions was introduced in Scheme and requires handling references to functions as closures instead of bare function pointers,
Функции высшего порядка: возвращающие функции как результаты
Возвращая функцию, мы на самом деле возвращаем ее замыкание. В примере на C любые локальные переменные, захваченные замыканием, выйдут из области видимости, как только мы вернемся из функции, создающей это замыкание. Попытка использовать замыкание позднее приведет к неопределенному поведению и, возможно, к повреждению стека. Эта проблема известна как проблема "восходящих аргументов функции" (upward funarg problem).
Равенство функций
Поскольку большинство литералов и значений можно проверить на равенство, естественно задаться вопросом, может ли язык программирования поддерживать проверку функций на равенство. При более детальном рассмотрении этот вопрос оказывается сложнее, и необходимо различать несколько типов равенства функций:
Extensional equality Two functions f and g are considered extensionally equal if they agree on their outputs for all inputs (∀x. f(x) = g(x)). Under this definition of equality, for example, any two implementations of a stable sorting algorithm, such as insertion sort and merge sort, would be considered equal. Deciding on extensional equality is undecidable in general and even for functions with finite domains often intractable. For this reason no programming language implements function equality as extensional equality. Intensional equality Under intensional equality, two functions f and g are considered equal if they have the same "internal structure". This kind of equality could be implemented in interpreted languages by comparing the source code of the function bodies (such as in Interpreted Lisp 1.5) or the object code in compiled languages. Intensional equality implies extensional equality (assuming the functions are deterministic and have no hidden inputs, such as the program counter or a mutable global variable.) Reference equality Given the impracticality of implementing extensional and intensional equality, most languages supporting testing functions for equality use reference equality. All functions or closures are assigned a unique identifier (usually the address of the function body or the closure) and equality is decided based on equality of the identifier. Two separately defined, but otherwise identical function definitions will be considered unequal. Referential equality implies intensional and extensional equality. Referential equality breaks referential transparency and is therefore not supported in pure languages, such as Haskell.
Экстенсиональное равенство. Две функции f и g считаются экстенсионально равными, если они выдают одинаковые результаты для всех входных данных (∀x. f(x) = g(x)). Согласно этому определению равенства, например, любые две реализации стабильного алгоритма сортировки, такие как сортировка вставками и сортировка слиянием, будут считаться равными. Определение экстенсионального равенства в общем случае неразрешимо, и даже для функций с конечными областями определения часто является вычислительно сложной задачей. По этой причине ни один язык программирования не реализует равенство функций как экстенсиональное равенство.
Extensional equality Two functions f and g are considered extensionally equal if they agree on their outputs for all inputs (∀x. f(x) = g(x)). Under this definition of equality, for example, any two implementations of a stable sorting algorithm, such as insertion sort and merge sort, would be considered equal. Deciding on extensional equality is undecidable in general and even for functions with finite domains often intractable. For this reason no programming language implements function equality as extensional equality. Intensional equality Under intensional equality, two functions f and g are considered equal if they have the same "internal structure". This kind of equality could be implemented in interpreted languages by comparing the source code of the function bodies (such as in Interpreted Lisp 1.5) or the object code in compiled languages. Intensional equality implies extensional equality (assuming the functions are deterministic and have no hidden inputs, such as the program counter or a mutable global variable.) Reference equality Given the impracticality of implementing extensional and intensional equality, most languages supporting testing functions for equality use reference equality. All functions or closures are assigned a unique identifier (usually the address of the function body or the closure) and equality is decided based on equality of the identifier. Two separately defined, but otherwise identical function definitions will be considered unequal. Referential equality implies intensional and extensional equality. Referential equality breaks referential transparency and is therefore not supported in pure languages, such as Haskell.
Интенсиональное равенство. При интенсиональном равенстве две функции f и g считаются равными, если они имеют одинаковую "внутреннюю структуру". Этот вид равенства может быть реализован в интерпретируемых языках путем сравнения исходного кода тел функций (например, в Interpreted Lisp 1.5) или объектного кода в компилируемых языках. Интенсиональное равенство подразумевает экстенсиональное равенство (при условии, что функции детерминированы и не имеют скрытых входных данных, таких как счетчик программы или изменяемая глобальная переменная).
Extensional equality Two functions f and g are considered extensionally equal if they agree on their outputs for all inputs (∀x. f(x) = g(x)). Under this definition of equality, for example, any two implementations of a stable sorting algorithm, such as insertion sort and merge sort, would be considered equal. Deciding on extensional equality is undecidable in general and even for functions with finite domains often intractable. For this reason no programming language implements function equality as extensional equality. Intensional equality Under intensional equality, two functions f and g are considered equal if they have the same "internal structure". This kind of equality could be implemented in interpreted languages by comparing the source code of the function bodies (such as in Interpreted Lisp 1.5) or the object code in compiled languages. Intensional equality implies extensional equality (assuming the functions are deterministic and have no hidden inputs, such as the program counter or a mutable global variable.) Reference equality Given the impracticality of implementing extensional and intensional equality, most languages supporting testing functions for equality use reference equality. All functions or closures are assigned a unique identifier (usually the address of the function body or the closure) and equality is decided based on equality of the identifier. Two separately defined, but otherwise identical function definitions will be considered unequal. Referential equality implies intensional and extensional equality. Referential equality breaks referential transparency and is therefore not supported in pure languages, such as Haskell.
Ссылочное равенство. Учитывая непрактичность реализации экстенсионального и интенсионального равенства, большинство языков, поддерживающих проверку функций на равенство, используют ссылочное равенство. Всем функциям или замыканиям присваивается уникальный идентификатор (обычно адрес тела функции или замыкания), и равенство определяется на основе равенства идентификаторов. Два отдельно определенных, но в остальном идентичных определения функции будут считаться неравными. Ссылочное равенство подразумевает интенсиональное и экстенсиональное равенство. Ссылочное равенство нарушает референциальную прозрачность и поэтому не поддерживается в чистых языках, таких как Haskell.
Extensional equality Two functions f and g are considered extensionally equal if they agree on their outputs for all inputs (∀x. f(x) = g(x)). Under this definition of equality, for example, any two implementations of a stable sorting algorithm, such as insertion sort and merge sort, would be considered equal. Deciding on extensional equality is undecidable in general and even for functions with finite domains often intractable. For this reason no programming language implements function equality as extensional equality. Intensional equality Under intensional equality, two functions f and g are considered equal if they have the same "internal structure". This kind of equality could be implemented in interpreted languages by comparing the source code of the function bodies (such as in Interpreted Lisp 1.5) or the object code in compiled languages. Intensional equality implies extensional equality (assuming the functions are deterministic and have no hidden inputs, such as the program counter or a mutable global variable.) Reference equality Given the impracticality of implementing extensional and intensional equality, most languages supporting testing functions for equality use reference equality. All functions or closures are assigned a unique identifier (usually the address of the function body or the closure) and equality is decided based on equality of the identifier. Two separately defined, but otherwise identical function definitions will be considered unequal. Referential equality implies intensional and extensional equality. Referential equality breaks referential transparency and is therefore not supported in pure languages, such as Haskell.
Теория типов
В теории типов тип функций, принимающих значения типа A и возвращающих значения типа B, может быть записан как A → B или BA. В рамках соответствия Карри — Ховарда типы функций связаны с логической импликацией; лямбда-абстракция соответствует разряду гипотетических предпосылок, а применение функции — правилу вывода modus ponens. Помимо обычного случая программирования функций, теория типов также использует функции первого класса для моделирования ассоциативных массивов и подобных структур данных. В категорно-теоретических подходах к программированию наличие функций первого класса соответствует предположению о замкнутой категории. Например, просто типизированное лямбда-исчисление соответствует внутреннему языку декартовых замкнутых категорий.