Функциональное программирование на Python. Часть 1. Общие вопросы

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

Парадигмы программирования

Для начала обратимся к википедии за определением понятия “парадигма программирования”. Парадигма программирования — это совокупность идей и понятий, определяющих стиль написания компьютерных программ (подход к программированию). Это способ концептуализации, определяющий организацию вычислений и структурирование работы, выполняемой компьютером.

Выделяют две крупные парадигмы программирования: императивная и декларативная.

Императивное программирование предполагает ответ на вопрос “Как?”. В рамках этой парадигмы вы задаете последовательность действий, которые нужно выполнить, для того чтобы получить результат. Результат выполнения сохраняется в ячейках памяти, к которым можно обратиться в последствии. 

Декларативное программирование предполагает ответ на вопрос “Что?”. Здесь вы описываете задачу, даете спецификацию, говорите, что вы хотите получить в результате выполнения программы, но не определяете, как этот ответ будет получен.

Каждая из этих парадигм включает в себя более специфические модели. В промышленности наибольшее распространение получили структурное и объектно-ориентированное программирование из группы “императивное программирование” и функциональное программирование из группы “декларативное программирование”.

В рамках структурного подхода к программированию основное внимание сосредоточено на декомпозиции – разбиении программы/задачи на отдельные блоки / подзадачи. Разработка ведётся пошагово, методом “сверху вниз”. Наиболее распространенным языком, который предполагает использование структурного подхода к программирования является язык C, в нем, основными строительными блоками являются функции.

В рамках объектно-ориентированного (ООП) подхода программа представляется в виде совокупности объектов, каждый из которых является экземпляром определенного класса, классы образуют иерархию наследования. ООП базируется на следующих принципах: инкапсуляция, наследование, полиморфизм, абстракция. Примерами языков, которые позволяют вести разработку в этой парадигме являются C#, Java.

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

Довольно часто бывает так, что дизайн языка позволяет использовать все перечисленные парадигмы (например Python) или только часть из них (например, C++).

В чем суть функционального программирования?

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

  • Функции являются объектами первого класса (First Class Object). Это означает, что с функциями вы можете работать, также как и с данными: передавать их в качестве аргументов другим функциям, присваивать переменным и т.п.
  • В функциональных языках не используются переменные (как именованные ячейки памяти), т.к. там нет состояний, а т.к. нет переменных, то и нет операции присваивания, как это понимается в императивном программировании.
  • Рекурсия является основным подходом для управления вычислениями, а не циклы и условные операторы.
  • Используются функции высшего порядка (High Order Functions). Функции высшего порядка – это функций, которые могут в качестве аргументов принимать другие функции.
  • Функции являются “чистыми” (Pure Functions) – т.е. не имеют побочных эффектов (иногда говорят: не имеют сайд-эффектов).
  • Акцент на том, что должно быть вычислено, а не на том, как вычислять.
  • Фокус на работе с контейнерами (хотя это спорное положение).

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

Примеры работы в функциональном стиле

Функции, как объекты первого класса

Ниже представлен код, который демонстрирует возможность Python, в части того, что функцию можно присвоить переменной и использовать ее:

>>> def mul5(value):
        return value*5

>>> v1 = 3
>>> f1 = mul5
>>> f1(v1)
15

Переменной f1, в качестве значения, присваивается функция mul5, после этого появляется возможность вызвать ее как функцию.

Рекурсия

Рекурсия – это вызов функции из нее же самой. Для начала приведем пример не рекурсивной функции, которая считает факториал:

def fact_iter(n):
   if n == 0 or n == 1:
       return 1
   else:
       prod = 1
       for i in range(1, n + 1):
           prod *= i
       return prod

print(fact_iter(5))

В качестве результата получим число 120.

Перепишем ее через рекурсию:

def fact_rec(n):
   if n == 0 or n == 1:
       return 1
   else:
       return n * fact_rec(n - 1)

print(fact_rec(5))

Результат получим аналогичный первому. Заметим, что такая реализация не является эффективной по памяти, о том почему это так, и как сделать правильно см “О рекурсии и итерации

Функции высшего порядка

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

>>> fn = lambda x: x**2
>>> print(list(map(fn, [1, 2, 3, 4, 5])))
[1, 4, 9, 16, 25]

Чистые функции

Чистые функции – это функции, которые не имеют побочных эффектов. В Python это не выполняется. Необходимо самостоятельно следить за тем, чтобы функция была чистой.

Например, следующая функция модифицирует данные, которые в нее передаются:

def fun_not_clear(data):
    if len(data) > 0:
        data[0] += 10

    return data*2

d1 = [1, 2, 3]
d2 = fun_not_clear(d1)

>>> print(d1)
[11, 2, 3]

>>> print(d2)
[11, 2, 3, 11, 2, 3]

В функцию fun_not_clear был передан массив данных, над которым был проведен ряд модификаций и сформирован новый массив. При этом исходный массив тоже был изменен. Это пример побочного эффекта, который можно получить в Python. Более корректный вариант будет выглядеть так:

def fun_clear(data):
    data = data[:]
    if len(data) > 0:
        data[0] += 10

    return data*2

d1 = [1, 2, 3]
d2 = fun_clear(d1)

>>> print(d1)
[1, 2, 3]

>>> print(d2)
[11, 2, 3, 11, 2, 3]

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

P.S.

Вводные уроки по “Линейной алгебре на Python” вы можете найти соответствующей странице нашего сайта. Все уроки по этой теме собраны в книге “Линейная алгебра на Python”.
Книга: Линейная алгебра на Python
Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas.  Для начала вы можете познакомиться с вводными уроками. Все уроки по библиотеке Pandas собраны в книге Pandas. Работа с данными”.
Книга: Pandas. Работа с данными

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *