Skip to content

Latest commit

 

History

History
143 lines (85 loc) · 17.4 KB

functional-programming.md

File metadata and controls

143 lines (85 loc) · 17.4 KB

Функциональное программирование как парадигма: цели, способы, применимость

Telegram: @piratestories | Гайд | Книги | Статьи, доклады, ресурсы |

Конспект вебинара "Функциональное программирование как парадигма: цели, способы, применимость" Вебинар провел Алексей Пирогов, разработчик, преподаватель Hexlet.

  1. Функциональное программирование, а что это?
  2. Зачем нужна ещё одна парадигма?
  3. Полезно и применимо ли ФП в обычной жизни программиста?

Предыстория ФП.

Сначала появилось Лямбда-исчисление (λ-исчисление), которое позволяло, на тот момент, любую вычислительную человеческую дейтельность свести к набору небольших примитивов и, таким образом, рассуждать об этом с математической точки зрения. Позже появились Машины Тьюринга и тезис Чёрча-Тьюринга.

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

Несмотря на то, что исторически Лямбда-исчесления пришли на компьютеры через LISP, данный язык, в дальнейшем, стал гибридным и давно перестал быть чисто функциональным.

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

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

Что такое ФП?

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

Чистые функции похожи на функции из математики. У них есть только аргументы и они возвращают только результат. Они не пишут в консоль, они пишут в БД, они не генерируют случайные числа, не ходят в сеть и т.д. Такая функция, при одних и тех же аргументах, возвращает одно и тоже значение. Она не зависит ни от чего, кроме аргументов.

Ключевые свойства чистой функции:

— не обладает побочными эффектами — является детерминированной

Наличие только одного из свойств недостаточно, для того чтобы функция была чистой.

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

Логику, почти всегда, можно описать чисто, а сайд-эффекты (выход в сеть, обращение к диску, и т.д.) можно описать в какой-то отдельной части программы.

Большинство ЯП с элементами ФП не позволяют, посмотрев на функцию, определить чистая она или нет. Для этого нужно смотреть в её исходники и анализировать код.

Ключевые свойства ФП:

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

Для того, чтобы нормально (а не просто filter, map, reduce применять вместо циклов) писать в ФП стиле, нужно чтобы:

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

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

Данных двух инструментов достаточно, чтобы писать функционально.

Зачем нужно ФП?

  • чистый код
  • ленивые вычисления (при условии чистоты)

Чистый код просто:

  • анализировать (рассуждать о)
  • тестировать
  • переиспользовать
  • вычислять параллельно и/или конкурентно

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

При тестировании чистых функции не нужно ничего мокать, подсовывать тестовые данные и т.д. Возможен Property-based testing.

Чистые функции легко переиспользовать потому, что мы можем код скопировать в другое место и знать, что мы не забыли перенести глобальную переменную, не забыли инициализировать в scope какой-то коннектор к БД т.д. Это просто удобно.

Чистые функции легко запускать конкурентно и/или параллельно, так как они НИКОГДА не будут менять данные (Erlang).

Про ФП в СИКП, LISP, Scheme, Clojure, JS, Python...

LISP — не функциональный ЯП. Типичный код на lisp состоит из присваиваний и изменений списков. Это процедурный код! Так же и ооп — это не лиспы.

СИКП — это книга не про ФП. Она использует некоторые моменты, свойственные ФП, но это книга про информатику вобще.

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

Clojure — лиспоподобный язык, но функциональный. Тут стоит разделять: lisp != (), a () != lisp. Clojure вдохновлялась лиспом на этапе зараждения каких-то отдельных концепций, но это functional first - язык. Все структуры данных, которые по-умолчанию есть в clojure — иммутабельные. Функции не всегда чистые и разделения на чистые/грязные там нет, но и нет практики часто использовать присваивание. На clojure хорошо пишутся программы с высокой степенью concurrency.

В Python применяются элементы ФП, но их принято избегать. Считается, что вместо map или filter лучше написать цикл. Хотя, стоит отметить, что использование только map, filter, reduce не делает код функциональным потому, что это всего лишь синтаксический сахарок, чтобы в некоторых случаях писать не цикл, а чуть по-короче. ФП далеко не только про это.

GO — не функциональный. Elixir — функционалный, в первую очередь.

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

Что важнее: чистый код или удобный интерфейс для конечного пользователя?

Некоторые люди думают, что ФП только про чистые функции. Это не правда. Функциональное программирование — про разделение чистых функций от грязных! В программе, которая делает что-то полезное, всегда будут side effects. Нам все равно надо ходить в сеть, нам надо ходить на диск, ходить в БД, генерировать случайные числа — это все равно будет.

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

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

Как получить максимальную пользу от ФП?

Писать функционално:

  • компиляторы, парсеры, генераторы
  • сложную логику с DSL
  • высококонкурентный код (О, привет, Erlang!)
  • GUI (О, привет, React!)

У Facebook система фильтрации спама написана на Haskell.

Как научиться ФП?

  • в вузе
  • изучить Haskell во имя добра!
  • изучить Clojure (ибо lisp и макросы)
  • изучить Erlang (ибо акторы)
  • изучить Prolog (чтобы пощупать логическое программирование)

Типы в яп — просто дополнительный инструмент для рассуждения о коде. Часть гарантий того, что у нас функция не делает side-эффектов, в haskell, например, реализуется за счет типов.

Когда говорят, что язык — это всего лишь инструмент, да — это правда. Но, для того, чтобы уметь этим инструментом пользоваться, нужны навыки.

Лучше писать код, понимая, что ты делаешь, чем не понимая. Человек, который знает Prolog и SQl, лучше пишет на SQL, чем человек, знающий только SQL.

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

Надо иметь не набор языков, а набор парадигм:
  • нужно уметь ООП (позволяет управлять сложностью процедурного кода)
  • нужно уметь ФП (многие задачи лучше и легче решаются на фп)
  • нужно уметь логичесекое программирование (чтобы развивать декларативное мышление)

Более менее универсальным рабочим ЯП можно назвать Python. Во многих областях одновременно является вторым языком.

Про ФП в JS

JavaScript вообще не пытается учить разработчика писать хорошо. Язык имеет явные проблемы в дизайне. Пытается "сразу на всех стульях усидеть" и всё сразу предоставить. Поэтому решить для себя в какой парадигме стоит на нём писать довольно сложно.

Нужно хорошо уметь пользоваться разными подходами, чтобы javascript по-разному использовать. Поэтому человек, который знает haskell, сможет писать на JS функционально хорошо и правильно (а не только с элементами фп). Научиться писать функционально на js самому, не имея опыта на других фп языках, весьма сложно.

Отдельно про ООП

Про ооп есть в сикп. Только там про объектный подход в целом. Объектно-ориентированное программирование не всегда те самые, пресловутые, "наследование, инкапсуляция, полиморфизм".

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

Алексей Пирогов про ФП