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

TODO: Определение функционального программирование и краткий ликбез

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

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

Термины друг на друга похожи, но давайте внесем ясность таким способом:

  • Функция высшего порядка - К таким функциям относится глобальная функция map, которая действительно получает другую функцию в качестве первого аргумента.
  • Функции первого класса - А этот термин мы косвенно осбуждали в разделе про функции, когда рассматривали работу оператора def и то что он в действительности создает экземпляр класса function, а это говорит нам о том, что мы можем работать с функциями как с обычными объектами, допустим присваивать их в переменные.
    def foo():
    pass
    foo2 = foo() # В этой строчке я только что присвоил в переменную `foo2` функцию `foo`

map/filter/sorted/reduce

Давайте мы с вами рассмотрим некоторые функции высшего порядка и попробуем самостоятельно их реализовать.

map

Функция map(function, iterable, ...) -> iterator получает 2 или более аргументов:

  • function - давайте будем называть ее "функция-изменятор"
  • iterable - итерируемый объект

Cама же функция map крайне простая она принимает "изменятор" и какую-то последовательность, после применяет "изменятор" к каждому элементу последовательности. Возвращает она итератор

LIST = [1, 2, 3]
def changer(i):
return i ** 2
sqr_list = list(map(changer, LIST))
print(sqr_list) # [1, 4, 9]

Собственная реализация(пример):

MOVIES = [
{
"id": 123
},
{
"id": 34224
}
]
def map(changer, iterable):
result = []
for i in iterable:
result.append(changer(i))
return result
def changer(movie):
movie["is_changed"] = True
return movie
print(map(changer, MOVIES))

filter

filter(function, iterable) -> iterator - функция похожа на map, но она не изменяет значения в последовательности, а фильтрует их. Получает она:

  • function - давайте будем называть ее "функция-фильтратор"
  • iterable - итерируемый объект

Функция так же как и map вернет итератор, в ходе работы которого она будет передавать значения из последовательности в функцию "фильтратор" и проверять проходит ли значение проверку или нет.

LIST = [1, 2, 3, 4, 5, 6]
def filtrator(i):
return i > 3
filtred_list = list(filter(filtrator, LIST))
print(filtred_list) # [4, 5, 6]

reduce

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

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

Пример

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

CATS = [
{
"id": 5523,
"name": "Barsik"
},
{
"id": 699,
"name": "Luparik"
},
#...
]

надо представить в виде:

CATS_INDEX = {
5523: {
"id": 5523,
"name": "Barsik"
},
699: {
"id": 699,
"name": "Luparik"
}
# ...
}

после этого мы сможем с вами получать кота по его id через CATS_INDEX[699] за O(1). Получается, что вместо списка котов мы получим словарь, где ключем является id кота, а значением ссылка на dict

from functools import reduce
CATS = [
{
"id": 5523,
"name": "Barsik"
},
{
"id": 699,
"name": "Luparik"
}
]
def cat_to_dict(acc, cat):
acc[cat["id"]] = cat
return acc
cats_index = reduce(cat_to_dict, CATS, {})
print(cats_index) # {5523: {'id': 5523, 'name': 'Barsik'}, 699: {'id': 699, 'name': 'Luparik'}}
print(cats_index[699]) # {'id': 699, 'name': 'Luparik'}

sorted

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

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

MOVIES = [
{
"id": 2340,
"title": "Forrest Gump",
"release": 1994
},
{
"id": 533,
"title": "Coco",
"release": 2017
},
{
"id": 99,
"title": "The Matrix",
"release": 1999
}
]
def key_extractor(m1):
return m1["release"]
sorted_movies = sorted(MOVIES, key=key_extractor)
print(sorted_movies)
r_sorted_movies = sorted(MOVIES, key=key_extractor, reverse=True)
print(r_sorted_movies)