Функции первого класса

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

В информатике язык программирования имеет функции первого класса, если он рассматривает функции как объекты первого класса. В частности, это означает, что язык поддерживает передачу функций в качестве аргументов другим функциям, возврат их как результат других функций, присваивание их переменным или сохранение в структурах данных [1] Некоторые теоретики языков программирования считают необходимым условием также поддержку анонимных функций. [2] В языках с функциями первого класса имена функций не имеют никакого специального статуса, они рассматриваются как обычные значения, тип которых является функциональным.[3] Термин был впервые использован Кристофером Стрэчи[en] в контексте «функции как объекты первого класса» в середине 1960-х. [4]

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

Существуют некоторые сложности в реализации передачи функций как аргументов и возвращении их как результата, особенно в присутствии нелокальных перемененных, введенных в вложенных и анонимных функциях. Исторически они были названы проблемами фунарга, от английского "function argument".[5] В ранних императивных языках программирования эти проблемы обходились путем отказа от поддержки возвращения функций как результата или отказа от вложенных функций и следовательно нелокальных переменных(в частности C). Lisp, один из первых функциональных языков программирования применяет подход динамической области видимости, где нелокальные переменные возвращают ближайшее определение этих переменных к точке, в которой функция была вызвана, вместо точки, в которой она была объявлена. Полноценная поддержка для лексического контекста функций первого порядка была введена в Scheme и предполагает обработку ссылок на функции как замыканий вместо чистых [4], что, в свою очередь, делает необходимым применение сборки мусора.

Концепции[править | править вики-текст]

В этой секции рассматривается, как конкретные идиомы программирования реализуются в функциональных языках с функциями первого порядка (Haskell) сравнительно с императивными языками, где функции - объекты второго порядка (C).

Функции высшего порядка: передача функции как аргумента[править | править вики-текст]

В языках, где функции - это объекты первого порядка, функции могут быть переданы как аргументы другим функциями так же как и любые другие значения. Так, например, в Haskell:

map :: (a -> b) -> [a] -> [b]
map f []     = []
map f (x:xs) = f x : map f xs

Языки, где функции не являются объектами первого порядка, позволяют реализовывать функции высшего порядка с помощью использования таких приемов как делегирование.

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

void map(int (*f)(int), int x[], size_t n) {
    for (int i = 0; i < n; i++)
        x[i] = f(x[i]);
}

Анонимные и вложенные функции[править | править вики-текст]

В языках, поддерживающих анонимные функции, можно передать такую функцию как аргумент функции высшего порядка:

main = map (\x -> 3 * x + 1) [1, 2, 3, 4, 5]

В языках, не поддерживающих анонимных функций, необходимо сперва связать[en] функцию с именем:

int f(int x) {
    return 3 * x + 1;
}
 
int main() {
    int l[] = {1, 2, 3, 4, 5};
    map(f, l, 5);
}

Нелокальные переменные и замыкания[править | править вики-текст]

Если язык программирования поддерживает анонимные или вложенные функции достаточно логично предполагать, что они будут ссылаться на переменные за пределами тела функции:

main = let a = 3
           b = 1
        in map (\x -> a * x + b) [1, 2, 3, 4, 5]

Если функции представлены в форме чистых , возникает вопрос того, как же передавать значения за пределами тела функции. В таком случае приходится строить замыкание вручную, и на этом этапе говорить о функциях первого класса не приходится.

typedef struct {
    int (*f)(int, int, int);
    int *a;
    int *b;
} closure_t;
 
void map(closure_t *closure, int x[], size_t n) {
    for (int i = 0; i < n; ++i)
        x[i] = (*closure->f)(*closure->a, *closure->b, x[i]);
}
 
int f(int a, int b, int x) {
    return a * x + b;
}
 
void main() {
    int l[] = {1, 2, 3, 4, 5};
    int a = 3;
    int b = 1;
    closure_t closure = {f, &a, &b};
    map(&closure, l, 5);
}

Функции высшего порядка: возврат функций как результата[править | править вики-текст]

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

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

  1. Structure and Interpretation of Computer Programs. — MIT Press, 1984. — ISBN 0-262-01077-1
  2. Programming language pragmatics, by Michael Lee Scott, section 11.2 "Functional Programming".
  3. «The Implementation of Lua 5.0».
  4. 1 2 Rod Burstall, "Christopher Strachey—Understanding Programming Languages", Higher-Order and Symbolic Computation 13:52 (2000)
  5. Joel Moses. "The Function of FUNCTION in LISP, or Why the FUNARG Problem Should be Called the Environment Problem". MIT AI Memo 199, 1970.

Литература[править | править вики-текст]

Ссылки[править | править вики-текст]