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

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

Версии презентации

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

Курс основан на книге 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. Идеи и навыки должны быть применимы к коду в любой ситуации, не обязательно начинать новый проект на ФЯП, чтобы применить эти навыки и идеи