суббота, 27 сентября 2014 г.

Простая таблица языков программирования

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

Языки
Императивные
Функциональные
Динамические
JavaScript, Ruby, Python, PHP
Clojure, Erlang, Scheme
Статические
Java, C#, C++, Go
Scala, Haskell, OCaml

понедельник, 22 сентября 2014 г.

Декомпозиция в ФП vs декомпозиция в императивном программировании

Любой программист разбивает программу на модули, модули на файлы, файлы на классы, классы на функции (если язык не объектно-ориентированный, то файлы сразу на функции). В среде императивных программистов есть устоявшийся набор правил, по которому эту декомпозицию надо делать. В основном, люди отталкиваются от принципов SOLID, KISS, DRY, Tell Don't Ask, закона Деметры, шаблонов проектирования, правил вроде "функция должна умещаться в экран" и т.п.
Забавно, но нигде из вышеупомянутых правил вообще нет никакого упоминания о side-эффектах. Схематично, программа, написанная в императивном стиле, выглядит примерно вот так:

Голубые прямоугольники ‒ это функции/классы, красные ‒ это строки с side-эффектами, желтые ‒ строки без side-эффектов.
Всё вроде бы хорошо, однако до тех пор, пока вам не приходится эти функции компоновать, т.е. объединять вызовы функций в более высокоуровневые функции, чтобы получилась программа. Часто бывает, что одна и та же функция может быть вызвана более 1 раза в проекте.
Почему трудно компоновать функции с side-эффектами? Потому что объединяя несколько функций в одну, вам нужно держать в голове все side-эффекты, которые каждая из функций содержит, и всё время анализировать: "устраивает ли меня, что серия вызовов вот этих функций кроме того что делает основную работу, ещё имеет следующий набор side-эффектов?"
Очень часто вот этот итоговый набор side-эффектов нежелателен. К примеру, функция f осуществляет запуск ракеты в космос и возвращает массу ракеты. Если вам нужно в проекте в 10 местах узнать массу ракеты, но запуск ракеты вам не нужен, то функция f для вас бесполезна! Вы её не сможете переиспользовать! Вы ведь не хотите, чтобы ракета запустилась тогда, когда пользователь запустил вашу программу, просто чтобы посмотреть характеристики этой ракеты?!
Что нужно сделать с функцией f(), чтобы её можно было использовать не только в случае реальной необходимости запуска ракеты, но и в других местах? Нужно выделить часть, отвечающую за запуск (side-эффект), в отдельную функцию, а часть, отвечающую за расчет массы ракеты, ‒ в другую функцию. Таким образом, вы отделяете чистые функции от функций с side-эффектами:

Вот эта желтая часть должна содержать большую часть вашей бизнес-логики и составляет где-то 70-80 процентов от всего проекта.
Что самое интересное, никто не мешает вам совмещать вот такое разделение с использованием вышеперечисленных привычных вам правил и законов. Хотите применять ООП, наследование и паттерны GoF (если вам так нравится) ‒ применяйте! Только отделяйте side-эффекты от чистых функций! Несколько паттернов, конечно, придется немного подкорректировать. Вот здесь описан, например, чистофункциональный визитор.
Выделение чистых функций, конечно, даётся не бесплатно. Но время, потраченное, на такую правильную декомпозицию, с лихвой окупается, когда дело доходит до компоновки и переиспользования функций! Чистые функции можно переиспользовать как угодно и сколько угодно, не задумываясь! Вы смотрите только на входные аргументы и на результат функции. Кроме того, чистые функции очень легко тестировать (любители TDD оценят).