Что такое функциональное программирование?

Функциональные языки программирования разрабатываются специально для создания приложений, ориентированных на обработку списков или символьные вычисления, так как функциональное программирование основывается на использовании математических функций. Для функционального программирования неплохо подходят такие языки, как Lisp, Python, Erlang, Haskell, Clojure и многие другие.

В последнее время функциональное программирование набирает популярность: многие традиционно императивные языки, такие как Java и Python, обеспечили поддержку его техник. Данная статья поможет понять и запомнить некоторые из техник функционального программирования.

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

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

В языке программирования Python функции  —  это объекты первого класса, поэтому для них доступны такие операции:

  1. Присваивать объекты функций переменным в качестве значений.
  2. Используя аргументы, передавать объекты функций другим функциям в качестве входных данных. 
  3. Хранить функции внутри структур данных, таких как словари. 
  4. Использовать функции в качестве значений, возвращаемых от других функций.

В приведенном ниже примере функция Show присваивается переменной Tell в качестве значения. Это присвоение не вызывает функцию для исполнения, потому что подразумевается именно объект функции, на который ссылается идентификатор Show:

def Show(Text):

    return Text.upper()

print(Show("Hello World"))

Tell = Show

print(Tell("Hello World"))

OUTPUT
HELLO WORLD
HELLO WORLD

Поскольку функции являются объектами, мы можем присвоить функцию Show любой переменной, а затем вызвать эту переменную для обращения к функции. 

Функции внутри структур данных

Функции могут храниться внутри структур данных, точно так же, как и все другие объекты языка Python. Например, можно создать словарь с ключами класса int, а значениями  —  класса function. Подобный словарь может пригодиться в том случае, когда ключ типа данных int отображает суть хранимой в качестве значения процедуры.

# СОЗДАНИЕ СЛОВАРЯ
dict = {
    0: func1,
   1: func2
}
x = input() # ПОЛУЧЕНИЕ ЦЕЛОГО ЧИСЛА ОТ ПОЛЬЗОВАТЕЛЯ
dict[x]() # ВЫЗОВ ФУНКЦИИ, ПОЛУЧЕННОЙ ПО КЛЮЧУ ИЗ СЛОВАРЯ

Функции как аргументы или возвращаемые значения

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

Рассмотрим пример

Допустим, необходимо перебрать список элементов, последовательно выводя их значения на экран  —  используем функцию iterate:

def iterate(list_of_items):
    for item in list_of_items:
        print(item)

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

Вот и пригодятся функции высшего порядка. Создадим функцию iterate_custom, которая принимает в качестве параметров итерируемый список и применяемую к его элементам функцию:

def iterate_custom(list_of_items, custom_func):
   for item in list_of_items:
        custom_func(item)

Хотя такая программа может показаться тривиальной  —  это невероятно мощное средство.

Мы добавили еще один уровень абстракции, сделав код более пригодным для повторного использования. Теперь можно вызывать функцию не только для печати списка, но и для любых действий, связанных с итерацией.

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

def add(x, y):
    return x + ydef sub(x, y):
    return x - ydef mult(x, y):
    return x * ydef calculator(opcode):
    if opcode == 1:
       return add
    elif opcode == 2:
       return sub
    else:
       return mult my_calc = calculator(2) #MY CALC IS A SUBSTRACOTR
my_calc(5, 4) #RETURNS 5 - 4 = 1 
my_calc = calculator(9) #MY CALC IS A MULTIPLLIER
my_calc(5, 4) #returns 5 x 4 = 20.

Вложенные функции

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

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

Допустим, необходимо определить функцию fib(n), возвращающую следующее число из последовательности Фибоначчи  —  nth, с одним аргументом  —  n.

Один из возможных способов определения такой функции  —  использование вспомогательной функции, которая отслеживает два предыдущих члена последовательности Фибоначчи (поскольку число Фибоначчи  —  это сумма двух предыдущих чисел Фибоначчи):

def fib(n):
    def fib_helper(fk1, fk, k):
        if n == k:
           return fk
        else:
           return fib_helper(fk, fk1+fk, k+1)    if n <= 1:
       return n
    else:
       return fib_helper(0, 1, 1)

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

Лямбда-выражения (функции одного выражения)

Как определить функцию, не указывая для неё идентификатор? Для этого созданы лямбда-функции, позволяющие определять короткие и линейные функции особым образом:

add = lambda x, y: x + y
add(1, 2) # ВОЗВРАЩАЕТ 2

Поведение функции из переменной add здесь точно такое же, как и поведение, определенное ранее с помощью традиционного ключевого слова def.

Обратите внимание, что лямбда-функции должны записываться в одну строку и не должны содержать явно указанный программистом оператор возврата return.

На самом деле, лямбда-функции всегда содержат неявный оператор возврата (в примере выше return возвращает результат операции x + y), но явные операторы возврата в лямбда-функциях отсутствуют.

Лямбда-функция намного мощнее и лаконичнее, потому что она позволяет создавать анонимные функции  —  функции без имени:

(lambda x, y: x * y)(9, 10) # ВОЗВРАЩАЕТ 90

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

import collections
pre_fill = collections.defaultdict(lambda: (0, 0))
# Все значения и ключи словаря установлены в целое число 0

Теперь настало время рассмотреть Map, Filter и Reduce, чтобы еще лучше оценить полезность лямбда-функций.

Map, Filter и Reduce

1. Map

map  —  это функция, принимающая на вход набор данных и преобразующая их в другой набор на основе заданной функции, то есть делающая то же самое, что и функция iterate_custom. Например:

def substract_1(x):    
    return x - 1

scores = [10, 9, 8, 7, 6, 5]
new_scores = list(map(substract_1, scores))
# scores ТЕПЕРЬ РАВНЯЕТСЯ [9, 8, 7, 6, 5, 4]
print(new_scores)

В Python 3 функция map возвращает объект класса map, который можно преобразовать в список для дальнейшего использования. Теперь, вместо явного определения функции multiply_by_four, можно определить лямбда-выражение:

new_scores = list(map(lambda x: X-4, scores))

2. Filter

Функция filter, как следует из названия, помогает “отфильтровать” нежелательные элементы из последовательности. Например, с её помощью можно легко и быстро отфильтровать все нечетные числа из списка scores:

even_scores = list(filter(lambda x: (x % 2 == 0), scores))
#even_scores = [6, 8]

Поскольку функция, предоставляемая для фильтрации, решает вопрос о принятии элемента в новую последовательность в каждом из конкретных случаев, то она должна возвращать значение bool (как показано в лямбда-функции выше) и быть унарной (принимать один входной параметр).

3. Reduce

Функция reduce полезна для “обобщения”, получения общей картины набора данных. Например, если необходимо вычислить сумму всех оценок, то reduce прекрасно с этим справится:

sum_scores = reduce((lambda x, y: x + y), scores)
# sum_scores = 32

Это гораздо проще, чем писать цикл. Обратите внимание, что функции reduce требуется предоставить два параметра: один представляет текущий проверяемый элемент последовательности, а другой  —  суммарный результат применяемой операции.

Все вышесказанное поможет основательно начать знакомство с функциональным программированием в Python.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Оригинал статьи: Function Programming with Python by Neel Gorasiya

Предыдущая статьяИИ-технологии на службе у инфлюенс-маркетинга
Следующая статья7 Must Visit ресурсов с идеями для веб-дизайна