Функциональное программирование — это интересная концепция программирования, которая в последнее время привлекает большое внимание. Эта статья представляет некоторые из наиболее важных аспектов функционального программирования в целом и предоставляет несколько примеров на Python.
Функциональное программирование — это своего рода парадигма декларативного программирования, где функции представляют отношения между объектами, как в математике. Таким образом, функции гораздо больше, чем обычные процедуры.
Эта парадигма программирования может быть реализована на разных языках. Существует несколько функциональных языков программирования, таких как Closure, Erlang или Haskel. Многие языки поддерживают функциональное программирование в дополнение к другим парадигмам: C ++, C #, F #, Java, Python, JavaScript и другие.
В этой статье вы найдете объяснения нескольких важных принципов и концепций, связанных с функциональным программированием:
- чистые функции,
- анонимные функции,
- рекурсивные функции,
- первоклассные функции,
- неизменные типы данных.
Чистые функции
Чистая функция — это функция, которая:
- идемпотент — возвращает тот же результат, если предоставлены одинаковые аргументы,
- не имеет побочных эффектов.
Если функция использует объект из более широкой области видимости или случайных чисел, связывается с файлами и т. Д., Это может быть нечистым, поскольку ее результат зависит не только от ее аргументов.
Функция, которая изменяет объекты вне своей области видимости, записывает в файлы, печатает на консоль и т. Д., Имеет побочные эффекты и также может быть нечистой.
Чистые функции обычно не используют объекты из внешних областей и поэтому избегают общих состояний. Это может упростить программу и помочь избежать некоторых ошибок.
Анонимные функции
Анонимные (лямбда) функции могут быть очень удобны для конструкций функционального программирования. У них нет имен и, как правило, они создаются специально для одной цели.
В Python вы создаете анонимную функцию с ключевым словом lambda:
1 |
lambda x, y: x + y |
Вышеприведенный оператор создает функцию, которая принимает два аргумента и возвращает их сумму. В следующем примере функции f и g делают то же самое:
1 2 3 |
>>> f = lambda x, y: x + y >>> def g(x, y): return x + y |
Рекурсивные функции
Рекурсивная функция — это функция, которая вызывает себя во время выполнения. Например, мы можем использовать рекурсию, чтобы найти факториал в функциональном стиле:
1 2 3 4 |
>>> def factorial_r(n): if n == 0: return 1 return n * factorial_r(n - 1) |
В качестве альтернативы, мы можем решить ту же проблему с помощью цикла while или for:
1 2 3 4 5 6 7 |
>>> def factorial_l(n): if n == 0: return 1 product = 1 for i in range(1, n+1): product *= i return product |
Функции первого класса
В функциональном программировании функции — это объекты первого класса, также называемые функциями высшего порядка — типы данных обрабатываются так же, как и другие типы.
Функции (или, точнее, их указатели или ссылки) могут передаваться в качестве аргументов и возвращаться из других функций. Их также можно использовать в качестве переменных внутри программ.
Приведенный ниже код иллюстрирует передачу встроенной функции max в качестве аргумента функции f и вызов ее изнутри f.
1 2 3 4 5 |
>>> def f(function, *arguments): return function(*arguments) >>> f(max, 1, 2, 4, 8, 16) 16 |
Очень важными концепциями функционального программирования являются:
- отображение,
- фильтрация,
- сокращение.
Все они поддерживаются в Python.
Отображение выполняется с помощью встроенной карты классов. Он принимает функцию (или метод, или любой вызываемый) в качестве первого аргумента и итерируемое (например, список или кортеж) в качестве второго аргумента и возвращает итератор с результатами вызова функции для элементов итерируемого:
1 2 |
>>> list(map(abs, [-2, -1, 0, 1, 2])) [2, 1, 0, 1, 2] |
В этом примере встроенная функция abs вызывается с аргументами -2, -1, 0, 1 и 2 соответственно. Мы можем получить тот же результат с пониманием списка:
1 2 |
>>> [abs(item) for item in [-2, -1, 0, 1, 2]] [2, 1, 0, 1, 2] |
Нам не нужно использовать встроенные функции. Можно предоставить пользовательскую функцию (или метод, или любую вызываемую функцию). Лямбда-функции могут быть особенно удобны в таких случаях:
1 2 |
>> list(map(lambda item: 2 * item, [-2, -1, 0, 1, 2])) [-4, -2, 0, 2, 4] |
Вышеприведенное утверждение умножило каждый элемент списка [-2, -1, 0, 1, 2] на 2 с помощью пользовательской (лямбда) функции lambda item: 2 * item. Конечно, мы можем использовать понимание, чтобы достичь того же:
1 2 |
>>> [2 * item for item in [-2, -1, 0, 1, 2]] [-4, -2, 0, 2, 4] |
Фильтрация выполняется с помощью встроенного фильтра классов. Он также принимает функцию (или метод, или любой вызываемый) в качестве первого аргумента и повторяемость в качестве второго. Он вызывает функцию для элементов итерируемого и возвращает новый итерируемый элемент, для которого функция вернула True, или все, что оценивается как True. Например:
1 2 |
>>> list(filter(lambda item: item >= 0, [-2, -1, 0, 1, 2])) [0, 1, 2] |
Вышеприведенная инструкция возвращает список неотрицательных элементов [-2, -1, 0, 1, 2], как определено функцией лямбда-элемента: item> = 0. Опять же, тот же результат может быть достигнут с использованием понимания :
1 2 |
>>> [item for item in [-2, -1, 0, 1, 2] if item >= 0] [0, 1, 2] |
Редукция выполняется с помощью функции Reduction из модуля functools. Опять же, он принимает два аргумента: функцию и повторяемость. Он вызывает функцию для первых двух элементов итерируемого, затем для результата этой операции и третьего элемента и так далее. Возвращает одно значение. Например, мы можем найти сумму всех элементов списка следующим образом:
1 2 3 4 |
>>> import functools >>> >>> functools.reduce(lambda x, y: x + y, [1, 2, 4, 8, 16]) 31 |
Этот пример только для иллюстрации. Предпочтительным способом вычисления суммы является использование встроенной функции уменьшения суммы:
1 2 |
>>> sum([1, 2, 4, 8, 16]) 31 |
Неизменные типы данных
Неизменяемый объект — это объект, состояние которого нельзя изменить после его создания. Наоборот, изменчивый объект допускает изменения в его состоянии.
Неизменяемые объекты обычно желательны в функциональном программировании.
Например, в Python списки изменчивы, а кортежи неизменны:
1 2 3 4 5 6 7 8 |
>>> a = [1, 2, 4, 8, 16] >>> a[0] = 32 # OK. You can modify lists. >>> a [32, 2, 4, 8, 16] >>> a = (1, 2, 4, 8, 16) >>> a[0] = 32 # Wrong! You can't modify tuples. Traceback (most recent call last): File "", line 1, in TypeError: 'tuple' object does not support item assignment |
Мы можем изменять списки, добавляя к ним новые элементы, но когда мы пытаемся сделать это с кортежами, они не изменяются, а создаются новые экземпляры:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
>>> # Lists (mutable) >>> a = [1, 2, 4] # a is a list >>> b = a # a and b refer to the same list object >>> id(a) == id(b) True >>> a += [8, 16] # a is modified and so is b - they refer to the same object >>> a [1, 2, 4, 8, 16] >>> b [1, 2, 4, 8, 16] >>> id(a) == id(b) True >>> >>> # Tuples (immutable) >>> a = (1, 2, 4) # a is a tuple >>> b = a # a and b refer to the same tuple object >>> id(a) == id(b) True >>> a += (8, 16) # new tuple is created and assigned to a; b is unchanged >>> a # a refers to the new object (1, 2, 4, 8, 16) >>> b # b refers to the old object (1, 2, 4) >>> id(a) == id(b) False |
Преимущества функционального программирования
Основные концепции и принципы — особенно функции высшего порядка, неизменные данные и отсутствие побочных эффектов — подразумевают важные преимущества функциональных программ:
- они могут быть проще для понимания, реализации, тестирования и отладки,
- они могут быть короче и более краткими (сравните две программы для вычисления факториала выше),
- они могут быть менее подвержены ошибкам,
- с ними легче работать при реализации параллельного выполнения.
Функциональное программирование — ценная парадигма, которую стоит изучить. В дополнение к перечисленным выше преимуществам, это, вероятно, даст вам новый взгляд на решение задач программирования.