Введение в функциональное программирование

Васильев Андрей Михайлович

2021

Краткий обзор курса

Курс основан на книге Grokking Simplicity Эрика Норманда

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

Необходимые знания для прохождения курса:

  • Базовое знание языка JavaScript
  • Понимание процесса выполнения JavaScript в рамках браузера

Императивное и декларативное программирование

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

Пример императивного подхода

Функция для вычисления суммы чисел на JavaScript

function sum(array) {
    let result = 0;
    for(let i = 0; i < array.length; i++) {
        result += array[i];
    }
    return result;
}

Примеры императивных языков программирования

Большинство языков общего назначения являются императивными: C, C++, Python, C#, Java, JavaScript, Ruby, Kotlin, Swift, PHP, Go

С их помощью мы можем описать произвольные алгоритмы для решения любых задач

Пример декларативного подхода

Выборка данных из базы данных на языке SQL

select name,surname from students where group='IT-11-MO'

Примеры декларативных языков программирования

Большинство предметно-ориентированных языков являются декларативными: SQL, HTML, XML, Prolog, QWL, XSLT, SPARQL

Данные языки реализованы зачастую на основе императивных языков программирования

Смешение подходов

Программисты заметили, что для решения задач в конкретной предметной области объём кода на декларативном языке заметно меньше кода на императивном языке. А чем меньше понятного кода написано, тем легче понимать данный код, тем легче его поддерживать.

Варианты внедрения декларативного подхода:

  • Некоторые языки программирования позволяют формировать предметно-ориентированные языки (domain specific languages, DSL) для решения поставленных задач наиболее кратким и удобным образом, например Ruby
  • Создание смешанных языков: QML (язык описания интерфейсов в Qt)
  • Внедрение техник функционального программирования: C++, C#, Python, JavaScript

Структурное, объектно-ориентированное

Все данные языки являются императивными языками программирования

Структурное и процедурное программирование

Исторически один из первых подходов к формированию программ, включает:

  • Последовательность действий, выполняемых друг за другом
  • Использование условий и циклов
  • Использование процедур для повторного использования кода
  • Описание собственных типов данных, структур

Объектно-ориентированное программирование

Ядром данной концепции является объект, который включает в себя данные и код, который обрабатывает эти данные

  • Обычно доступно большинство концепций структурного программирования
  • Большинство языков основывает систему объектов на классах или прототипах
  • Наследование, инкапсуляция, полиморфизм

Определение функционального программирования

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

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

Побочные эффекты

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

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

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

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

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

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

Неверная интерпретация определения ФП

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

Проблемы определений ФП

Программам нужны побочные эффекты

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

ФЯП удобны для работы с побочными эффектами

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

ФЯП практичны

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

ФП как набор навыков и концепций

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

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

Различие действий, вычислений и данных

С точки зрения ФП код можно разделить на 3 важных категории

Действия

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

То есть под действиями мы понимаем функции с побочными эффектами

Примеры таких функций:

  • sendEmail(to, from, subject, body) — отправляет почтовое сообщение
  • saveUserDB(user) — после сохранения данные будут доступны всей системе
  • getCurrentTime() — каждый вызов данной функции вернёт новые данные

Вычисления и данные

Вычисления и данные не зависят ни от времени обращения, ни от количества обращений. При любом обращении эти элементы будут возвращать корректные данные

Примеры:

  • {"firstname": "Bob"} — данные не изменяются со временем (самостоятельно)
  • sum(numbers) — сумма чисел зависит только от переданных чисел
  • string_length(string) — длина зависит от строки
  • [1, 10, 15, 5, 26] — массив не изменяется со временем

Разделяем вычисления и данные

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

  • Данные нейтральны, прозрачны для восприятия
  • Результат вычислений не понятен до его окончания

Работа с категориями в ФЯП

При работе над исходным кодом программисты на ФЯП различают все три категории

  • Все категории используются при написании приложений на ФЯП
  • У каждой категории есть свои плюсы и минусы
  • Данные лучше вычислений, вычисления лучше действий

Определение категорий

Рассмотрим следующий пример работы веб-приложения:

  1. Пользователь помечает в интерфейсе задачу как выполненную
  2. Веб-браузер посылает сообщение на сервер, описывающий действие пользователя
  3. Сервер получает сообщение
  4. Сервер обрабатывает данные и сохраняет их в базу данных
  5. Сервер выбирает корреспондентов для отправки почтовых сообщений
  6. Сервер высылает сообщение выбранным корреспондентам

Определим для каждого шага его категорию

Определение категорий

  1. Пользователь помечает в интерфейсе задачу как выполненную

    Это действие, т.к. оно зависит от количества вызовов

  2. Веб-браузер посылает сообщение на сервер, описывающий действие пользователя

    Сообщение является данными, но их отправка — это действие

  3. Сервер получает сообщение

    Получение сообщения — это действие, т.к. зависит от количества

  4. Сервер обрабатывает данные и сохраняет их в базу данных

    Изменение состояния БД — это действие

  5. Сервер выбирает корреспондентов для отправки почтовых сообщений

    Выбор корреспондентов зависит только от события, это вычисление

  6. Сервер высылает сообщение выбранным корреспондентам

    Отправка почты — это действие

Свойства трёх категорий исходного кода

Действия

Если код зависит от времени выполнения, количества запусков или того и другого, то это действие

  • Результат отправки срочного почтового сообщения сегодня или через неделю важно
  • Отправка сообщения один или десять раз важно

Вычисления

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

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

Данные

Данные обычно представляют собой записанные факты о произошедших событиях

  • Данные имеют смысл без выполнения
  • Данные можно интерпретировать различными способами

Рассмотрим чек из ресторана. На данных из чека можно

  • Менеджеру ресторана можно узнать какие блюда являются популярными
  • Покупателю можно отслеживать свои траты

Инструменты ФЯП

Действия

  • Средства для безопасного изменения состояния во времени
  • Подходы для гарантии последовательных вычислений
  • Инструменты для выполнения действий только один раз

Вычисления

  • Средства статического анализа для достижения корректности
  • Возможность применения математического аппарата
  • Эффективность применения автоматического тестирования

Данные

  • Подходы для организации эффективного доступа к данным
  • Дисциплины для сохранения данных на долгую перспективу
  • Принципы для сохранения важной информации в данных

Зачем заниматься категоризацией?

ФЯП по своей природе гораздо лучше работают в рамках распределённых систем по сравнению с другими языками

Большинство современных приложений — распределённые

  • Веб-приложения по определению общаются с сервером
  • Мобильные приложения зачастую завязаны на сервер
  • Серверные приложения сами по себе состоят из множества компонент

Проблемы сетевых распределённых приложений:

  • Сообщения могут придти не в правильном порядке
  • Сообщения могут дублироваться
  • Сообщения могут не доходить
  • Ответ на сообщение может тоже не дойти до отправляющей стороны

Т.е. возникают проблемы, связанные с моделированием изменений во времени: проблемы от количества вызовов и от времени вызовов

Чем категории могут помочь?

Данные и вычисления не зависят от количества обращений, ни от времени их выполнения. Чем больше данных и вычислений в приложении и чем меньше действий, тем меньше мест в приложении, где может возникнуть проблемы распределённых приложений

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

Что такое функциональное мышление

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

В рамках курса (и книги) постараемся овладеть этими инструментами, чтобы успешно применять их на практике

Базовые идеи ФП:

  • Разделение всего кода на действия, вычисления и данные
  • Использование функциональных абстракций и подходов

Правила выбора идей и навыков

Данные правила были использованы для выбора идей для книги, и следовательно будут рассмотены в курсе

  1. Они не должны быть основаны на уникальных особенностях функциональных языков программирования. Пример: мощная система типов, встроенные неизменяемые типы и т.п.
  2. Навыки должны быть применимы на практике и приносить пользу при программировании
  3. Идеи и навыки должны быть применимы к коду в любой ситуации, не обязательно начинать новый проект на ФЯП, чтобы применить эти навыки и идеи