Преимущества функционального программирования давно признаны широкой общественностью. Успешное развитие программного обеспечения нередко сводится к максимальному упрощению существующих механизмов, которые позволяют новым приложениям приспособиться к требованиям современных пользователей. А заодно приходится спешить, успевая в сжатые сроки представлять потребителям продукцию с неограниченными возможностями. Гораздо проще справиться с поставленной задачей, когда разрабатываемые приложения можно условно разделить на несколько чистых функций, проверить которые не составляет труда. В таких алгоритмах нет каверзных побочных эффектов и абстрактных формулировок, рассчитанных на результаты в глобальном масштабе.
Но, похоже, ни один функциональный язык не претендует на звание общепризнанного. Я имею в виду своеобразную всемирную гегемонию C в 70-х, который позже передал корону C ++, Java, а со временем и JavaScript. Ни один из претендентов второго эшелона (Python, PHP, Perl, Ruby, C# ...) не попадал в категорию функциональных.
А потом произошло что-то невероятное. Да, я говорю о Clojure.
Шучу! Никто не говорит, что на мировой арене появился новый функциональный язык, мгновенно ставший лидером, просто на этот раз мы явно имеем дело с растущим потенциалом чемпиона, который внезапно вышел на первый план. Сейчас мы работаем над созданием мобильного приложения на React/Redux, и вот как выглядит код, который я пишу:
aliasesList.reduce((allMatching, matching, index) => {
if (matching.count() > 0) {
const expanded = allAliases.take(index)
.concat(List.of(matching))
.concat(allAliases.skip(index + 1))
.filter(aliases => aliases.count())
.reduce((current, aliases) =>
current ? current.flatMap(
i1 => aliases.map(i2 => i1.concat(i2))
) :
aliases.map(alias => List.of(alias)),
null);
return allMatching.concat(expanded);
} else {
return allMatching;
}
}, new List());
Так почему же специалисты так долго упускали функциональное программирование из виду? И почему оно становится столь распространенным явлением сегодня?
Долой существительные
Ответ на первый вопрос во многом определяется одной из основных тенденций в сфере разработки программного обеспечения. После окончания университета меня с головой накрыло волнами объектно-ориентированного цунами, а потому более 20 лет я ни строчки не написал на LISP. Сообщество разработчиков ПО признало данную методологию Истиной в последней инстанции – через несколько лет с появлением Java практика лишь закрепилась — причем коллективный разум почему-то настойчиво игнорировал преимущества функционального программирования.
Вооружившись инкапсуляцией, наследованием и полиморфизмом, ООП дарило надежду на упрощение процесса создания новых программ. Однако, в последнее время недостатки метода становились все очевиднее. Жесткие иерархии объектов и отсутствие четких характеристик классов заметно тормозили эволюцию ООП. Постепенно неопределенность распространяется на все без исключения составные элементы программирования, а, значит, не обходится без нежелательной взаимосовмещаемости и непредсказуемых побочных эффектов. Именно так глаголы функционального программирования вытеснили существительные, на которых зиждилось ООП.
Я слегка пошутил, упомянув Clojure чуть раньше, но ведь именно это диалект способствовал возрождению функционального программирования в последнее время. Если говорить о целом ряде практических вопросов, которые не удавалось решить со старым добрым LISP, вместе с Clojure появилось целое поколение разработчиков-функционалов. Так был создан Оm, без которого невозможно представить эволюцию современной экосистемы JS (подробнее об этом позже).
JavaScript растет на глазах
Помимо разочарования ООП, два озвученных ранее вопроса также связаны в силу нескольких дополнительных тенденций: становления JavaScript в качестве полноценного языка программирования общего назначения и включения методологии функционального программирования в обширную экосистему JavaScript.
На рубеже десятилетий становится очевидно, что ключевую роль в большинстве веб-разработок по-прежнему играет сервер, в то время как клиентские скрипты выполняют вспомогательные функции. Да, отдельные приложения на Ajax, например, Gmail, продемонстрировали, насколько удобно использовать приложения в виде отдельной страницы, работающей в самом браузере, для которой сервер – в первую очередь, централизованное хранилище данных. Быстрые веб-приложения, напоминающие привычные стационарные приложения, оказались на порядок выше громоздких предшественников на CGI.
После выпуска Google Chrome в 2008 году и, в частности, после презентации движка V8 от JavaScript, тенденция только усилилась. Создание сверхскоростного JavaScript стало предварительным условием для разработки полноценных одностраничных приложений. И снова лидирует Google со своим AngularJS. V8 также упрочил позиции в Node.js, отдельной JS платформе, применяемой, в первую очередь, для разработки серверных продуктов.
Внезапно JavaScript перестал казаться детским языком программирования. Благодаря AngularJS и целому семейству аналогичных разработок, а с ними и Node, неимоверно возрос интерес к JS, что обусловило невероятный спрос на этот язык программирования в течение последних нескольких лет.
Функция и форма
И какое отношение все это имеет к функциональному программированию? Брендан Эйч (создатель JS) вспоминает, что в 1995 году его взяли в Netscape для создания схемы браузера. Хотя позже пришлось согласиться на уговоры руководства, настроенного получить продукт «похожий на Java», разработка сохранила ряд важных свойств языков функционального программирования (например, функции первого класса). Ее окрестили (пусть и с преувеличением) «LISP с синтаксисом C».
На просторах интернета миллион примеров, благодаря которым становится ясно, что с помощью JS можно написать императивный, объектно-ориентированный или просто отвратительный спагетти-код. Но, если сравнивать JavaScript с другими языками программирования, напоминающими С, именно он может стать отличным прототипом для создания истинного функционального языка.
В 2011 году в JS (технически ECMAScript) были добавлены функциональные команды «stalwarts map» и «reduce». В ECMAScript 2015 (ECMAScript 6) с его лаконичным синтаксисом и совершенно рациональной структурой, появился ряд других функциональных инструментов, включая создание объектов и полюбившуюся пользователям жирную стрелку. Далеко не все функции ES2015 поддерживаются браузерами, но благодаря транскомпилятору Babel это не беда, ведь вы можете использовать функционал ES2015 (и даже ES2016), а затем компилировать полученные результаты в виде универсального ES5.
Нормальная реакция
AngularJS ознаменовал начало бума JavaScript, но поскольку продукт корнями уходит в Java и ООП, не стоит записывать его в ряды функциональных разработок. За пару последних лет Facebook React превратился в отличный быстрый клиентский JS фреймворк. Да, не обошлось без впечатляющих показателей архитектуры Flux и библиотеки Immutable.js, также числящихся среди заслуг команды Facebook (интересно, насколько развитие функционального JS совпадает с тем, как Facebook вытесняет Google с рынка фреймворков JS).
Похоже, React формирует очень функциональный подход к разработке веб-приложений. По сути, последняя версия React представила упрощенный синтаксис для чистых компонентов, интеграция которых не вызывает побочных эффектов.
React специализируется, исключительно, на отображении данных, однако, именно поэтому Facebook рекомендует использовать Flux для расширения архитектуры приложений. Так как Flux — просто набор основных элементов (хранилища, действия, централизованный диспетчер служб, однонаправленный поток данных), одновременно с этой разработкой конкуренты поспешили создать собственные альтернативы, претендовавшие на звание лидера в своей сфере. Причем до появления Redux победителя в столь равном бою так и не было.
В основе механизма Redux лежат следующие принципы: единое централизованное хранилище данных, изменение состояния возможно только в результате конкретных действий и применения чистых функций (редьюсеры).
При использовании в сочетании с Immutable.js Redux становится едва ли не близнецом Om от Clojure. Теоретически, совместить функциональное программирование и JavaScript можно было всегда, но React, Redux и Immutable.js, наконец, предложили разработчикам JS практическое решение вопроса — разработку чистых редьюсеров без побочных эффектов, которые позволяют изменять привычное состояние продукта в рамках неповоротливого ES6 синтаксиса — для создания крупномасштабных функциональных приложений. Популярность такого подхода (и, следовательно, широкое распространение функционального программирования) просто поражает: когда я впервые обратил внимание не Redux несколько месяцев назад, у него было около 500 звезд на GitHub. Сегодня – более 11000.
Функциональное будущее?
Пока рано утверждать, что интерес к функциональности JavaScript продолжит расти. В последнее время JS неоднократно сталкивался с периодами нестабильности, а потому внезапное появление бесчисленного множества новых фреймворков, претендующих на исключительность, выглядело довольно забавно. Но богатая родословная функционального программирования и корни сформировавшейся тенденции выглядят весьма убедительно. И пусть отдельные проекты терпят неудачи, кажется, будущее разработки программного обеспечения, все же будет функциональным.
По традиции, немного рекламы в подвале, где она никому не помешает. Наша компания запустила новогоднюю распродажу серверов и VPS, в рамках которой можно получить от 1 до 3 месяцев аренды бесплатно. С подробностями акции вы можете ознакомиться тут.
Комментарии (35)
justaguest
05.01.2016 22:36-2Я не уверен, что подразумевает «звание общепризнанного», но Haskell, один из популярнейших функциональных языков, не упомянут :)
И коль упомянут Python — я бы назвал Haskell как раз статически типизированным питоном.devpony
06.01.2016 00:37+10Не надо называть Haskell «статически типизированным питоном», не оскорбляйте его.
justaguest
06.01.2016 00:49Не имел таких намерений. Просто в беседе с людьми, не знающими о предмете разговора, лучше всего применять аналогии — а питон довольно популярен, был упомянут в статье, и ближе всего по синтаксису (хотя, конечно, не по мышлению).
stalkerg
06.01.2016 01:43+5Преимущества функционального программирования давно признаны широкой общественностью.
А ещё больше признаны недостатки этого подхода, из-за чего нету и не предвидится его доминирование.andyN
06.01.2016 03:48-5Надеюсь Вы правы и доминирования не произойдет =) Лично я, когда вижу лапшекод из функций, не понимаю, как некоторым такой код кажется проще декларативного подхода. Это все возможно субъективщина, но лично я нагромождение функций воспринимаю плохо.
shoomyst
06.01.2016 01:46+7Что-то какая-то сумбурная статья получилась: всё в кучу намешано. Про какие-то войны корпораций с их фреймворками, которые бьются за мифическое место под солнцем, вытесняя друг друга. Остаться должен только один? Непонятно какой бум ознаменовал Ангулар? Знаю, jQuery ознаменовал клиентский бум, NodeJS — серверный. Ангулар — хоть и был значителен, но был лишь одним из. Был и backbone, был и ember, и knockout, и многое другое.
Ну и не сказал бы, что React весь пронизан функциональщиной. Требование иммутабельности — это так, стечение обстоятельств по большей части. Redux — да, но это лишь один Абрамов, в нужное время в нужном месте. Так что по-моему автор тут скорее выдает желаемое за действительное.
Ну а функциональщина это такой себе общий тренд на сегодня для многих языков в отрасли, и я не удивлюсь, что просто временно модный.
deniskreshikhin
06.01.2016 05:24+23Такое ощущение, что народ не хочет учить ООП, а признаться что ума хватает только на императивное программирование — стыдно. Поэтому все ринулись записываться стройными рядами в яростные поборники ФП. При этом под ФП они имеют ввиду обычное императивное программирование. Это кстати хорошо видно по примеру:
... const expanded = allAliases.take(index) .concat(List.of(matching)) .concat(allAliases.skip(index + 1)) .filter(aliases => aliases.count()) ...
Т.к. этот код есть наичистейшее императивное программирование, ведь все действия описаны явно в строгом порядке. Поменяй их местами и все полетит к чертям.
В функциональном программировании результат есть не следствие действий, а следствие вывода из других функций благодаря математическим связям. Порядок объявления функций и описания связей обычно вообще не важен.
А здесь выдается обычный fluent interface за ФП.
Дожили.KilgortTraut
06.01.2016 12:53+11Ну по вашей логике, тогда и математика отчасти императивная. Ведь a / b != b / a. И порядок действий в просчетах тоже важен. Так что я пожалуй с этим аргументом не согласен. Избегать зависимости от очередности выполнения нужно только в тех случаях, где это необходимо.
Так же тут не fluent interface, а pipeline, по очередное выполнение операций на новых данных, каждый метод возвращает новый instance данных. Fluent interface работает с одним объектом.
У Haskell похожее можно достичь с помощью functional composition (filter length. concat list2. concat list1. take index). Что по вашему здесь тоже императивное программирование?
Не важно как записано, по сути это и есть описанный вами вывод на основе математических функций.
deniskreshikhin
06.01.2016 14:12Проблема не в том, где и как можно писать. В Smalltalk такие вещи можно было писать уже в 70-е годы.
Проблема в методологии разработки, ФП подразумевает декомпозицию решения на структуры данных и функции в которых персистентность, чистота и ленивость приводят к реальному эффекту в виде упрощения и ускорения работы программы. Об это у Окасаки есть целая книжка.
Здесь да, присутствует collection pipeline, но:
1. Промежуточные данные нигде не используется, а значит профита от персистентности нет вообще никакого.
2. В js итак map, concat и т.п. методы всегда возвращали новый инстанс, уже лет 15. Без всяких фреймворков.
Т.е. манипулирование коллекциями в таком стиле ввел еще Алан Кей в Smalltalk (ООП язык кстати) в 70-е. Почему это все воспринимают как ФП не понятно.
Да и сам Мартин Фаулер пишет
But loops aren't the only way to represent list processing, and in recent years more people are making use of another approach, which I call the collection pipeline. This style is often considered to be part of functional programming, but I used it heavily in Smalltalk.
Flammar
06.01.2016 15:06Для pipeline'ов используются сегодня не столько коллекции, сколько стримы.
deniskreshikhin
06.01.2016 16:10Да не имеет значения — стрим, итератор, курсор, генератор все это инструменты для избежания прямого обращения к элементам по индексу/ключу.
KilgortTraut
07.01.2016 01:57+2То, что это можно было делать за царя гороха в Smalltalk(ООП язык типа да) — не значит, что это не функциональные фишки. Одно другое не исключает. И идеи довольно просты, полезны и очевидны, тут согласен, что многие из них делают слишком много шума(мода, что тут сказать)
Функциональное программирование вообще то самая древняя парадигма, появилась раньше ООП, так что тут кто у кого заимствовал еще(если речь об этом конечно). В целом есть такая тенденция, что его хотят запихнуть везде, где нужно и не нужно, но от этого оно хуже не становиться, главное рационально понимать его применение и проблемы, которое оно может решить эффективней того же ООП.Flammar
07.01.2016 14:26Одно из важных достоинств ООП — это возможность эмуляции некоторых частей функционального программирования путём использования «паттерна Стратегия» через механизмы наследования или имплементации, когда функцию оборачивают в «объект первого класса», фактически синглетон. Собственно, большая часть классических «шаблонов проектирования» — именно про это.
deniskreshikhin
07.01.2016 15:20-1Да ничего функционального в приведенном участке кода нет. Вот о чем я, а не кто первее.
Я уже не говорю про вызов аля `.take(index)`, что априори является диким моветоном в Haskell, Erlang и т.д.
Что касается map, reduce и т.д. то я проиллюстрирую следующим примером:
// 1980 - Smalltalk paths reject: [:each | each isDirectory]) collect: [:each | each name] // 1994 - Python reduce(lambda x,y: x+y, map(lambda x: x**2, range(1, 100)), 0) // 1995 - Ruby (1..100).map{|x| x**2}.reduce(:+) // 1998 - Lua ld.map({1, 2, 3, 4, 5}, function(n) return n * 2 end)) // 2016, почти 20+ лет спустя // -- КО-КО-КО, смотрите Я УМЕЮ ФП, типерь я ФП-программист, пыщ-пыщ!!1 const expanded = allAliases.take(index) .concat(List.of(matching)) .concat(allAliases.skip(index + 1)) .filter(aliases => aliases.count()) .reduce((current, aliases) => current ? current.flatMap( i1 => aliases.map(i2 => i1.concat(i2)) ) : aliases.map(alias => List.of(alias)), null);
varanio
08.01.2016 11:09Сейчас даже в php есть генераторы, а значит можно накостылять какой-нибудь пайп-механизм. Прям не знаю, нужно ли делать целый язык под это дело
Flammar
07.01.2016 14:18+2Такие гибриды коллекций с итераторами как односвязные списки в качестве одного из базовых типов данных появились ещё задолго до ООП в родоначальнике функционального программирования (и вообще втором по счёту языке программирования высокого уровня после Фортрана) языке LISP. Односвязных списков вполне достаточно для маппинга, фильтрации и редьюсинга. Односвязный список — вполне себе такой «сам себе стрим/однонаправленный итератор», ибо у него есть только «голова»==текущий элемент и ссылка на следующий элемент, дополнительным плюсом от чего являются возможность приделать несколько «голов» к одному и тому же «хвосту» и возможность создавать «ленивые списки», в которых хвост генерируется «на лету» только при обращении.
Flammar
06.01.2016 15:03+1Судя по всему, в примере не просто fluent interface, а методы, применяемые к
allAliases
и дальше, не изменяют объект, а создают новый, возможно, «ленивый».
kekekeks
06.01.2016 11:10+2Сейчас мы работаем над созданием мобильного приложения на React/Redux, и вот как выглядит код, который я пишу:
Вы сейчас почти дословный аналог куска шарпокода с активным использованием LINQ показали. Что забавно, LINQ в шарпе появился в год выпуска Clojure.VolCh
06.01.2016 12:21Скорее не забавно, а закономерно. Функциональные фичи активно внедряются в императивные мэйнстрим языки.
Myshov
08.01.2016 07:07Один из авторов linq в одной из лекций по функциональному программированию говорил, что при создании linq они черпали вдохновление из haskell.
denismaster
06.01.2016 12:38+3Языки программирования развиваются. Что объектные языки получают удобные функциональные вещи, так и рассмотренный в статье «функциональный» JS получает элементы ООП. Во многих языках — мультипарадигма.
Наша задача — писать эффективно эффективный код, а не проводить время в вечных спорах)
occam
06.01.2016 23:48-8Уверен, что пайтон — это не просто кандидат, а лучшая и прямая замена всем архаичным функциональным языкам
Yuuri
07.01.2016 00:01+3Архаичным – это которым? Что именно делает их архаичными? И почему именно Python?
occam
14.01.2016 22:58-2Видите ли в силу моей убежденности о создании мира высшим разумом, я не верю в постулаты о строгом математическом устройстве разума, вселенной вообще и технический анализ в частности. Поэтому мой ответ: все существующие ФЯ архаичны. В силу своей практической бесполезности (кроме разве что чисто инженерных задач, в которых 100% переменных известны и проверены практикой). Они как кремневые мушкеты, на первый взгляд, круты в силу своей исторической ценности ну и на этом, пожалуй, все. Прошу понимания — ведь я не стремился к спору, а сообщил о своей личной внутренней уверенности.
Пайтон упомянут не всуе, а благодаря тому, что он позволяет максимально уравнять шансы всех участников эстафеты от новичков до гуру, т.е. как наиболее близкий к представлению о языке (а следовательно, и средстве производства) равных возможностей.occam
14.01.2016 23:04-2Плюс при необходимости, когда она есть, в пайтоне эффективно, как мне кажется, применяются методы функционального программирования.
j_wayne
Я думаю, что stores правильнее перевести все же как «хранилища», а не «магазины»
VolCh
Чёрт, а я уже половину гадов прочитал в надежде посмотреть, что за фрейворк, где магазин основной элемент.
stas404
Заодно и «редьюсеры» вместо «редукторов».