Как стать автором
Обновить

JavaScript в 2016 году: функциональное программирование пришло всерьез и надолго

Время на прочтение 3 мин
Количество просмотров 28K
Всего голосов 89: ↑46 и ↓43 +3
Комментарии 88

Комментарии 88

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

Статьи про функциональное программирование тоже очень короткие и простые.
Потому что невозможно написать что-либо крупное? ;)
Заметил такую тенденцию что статьи западных девелоперов называются очень громко го освещают очень мало
Скорее, не хватает нормальных статей для перевода и заполнения всех корпоративных блогов на хабре, а очень хочется.
Причина проста: такие статьи пишутся для галочек в резюме
> функциональная парадигма, которая получила распространение благодаря React

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

а что, нужны доказательства?

“Thou shalt not make unto thee any graven image,” или не нужно без нужды плодить идолов. В реальном мире если вы отломите ножку стулу, у вас в руках останется трехногий стул. Как бы ни удобно было работать с иммутабельными объектами, эта концепция противоречит реальному миру и за обходы этого противоречия приходится платить.

Это не значит, что чистые функции и иммутабельные структуры это плохо. Но это достаточно веский аргумент, чтобы сказать «чистые функции и иммутабельные структуры — это по-разному». Микроскоп, несомненно, более изысканный инструмент, но гвозди лучше забивать утюгом.
Отличный пример ценности иммутабельности. Вы хотите использовать трехногий стул точно так же, как и целый, а ведь это совершенно новая структура со своими функциями (надо сказать, кардинально отличающимися от исходных).
> Вы хотите использовать трехногий стул точно так же, как и целый

Почему это? Это ниоткуда не следует. А может быть, хочу, но учтите, что я пользуюсь стульями исключительно как дровами для печки. Или еще что.

Любая фраза про любую парадигму, если она содержит слово «всегда», не является верной. Иммутабельность — это очень хорошо. Иногда. А иногда — нет.

Следует из семантики слова «стул», иначе это уже не программирование, а демагогия.
Ой, да вы семантический пурист :)

Ну раз вы не умеете мыслить абстрактно, вот вам абсолютно легитимный семантически пример: я арендую квартиру со стульями. Стулья — субстанция хрупкая, иногда ломаются. Покупка нового стула взамен старого — акт, требующий согласования с хозяевами. За каждую же отломанную ножку с меня просто вычтут 10 рублей, пока количество ног у стула более двух.

Сидеть я на стульях не планирую, уточню на всякий случай.

То, что вы описали нигде и не называется «стул», не то что в программировании. Это «предмет», «арендная единица», а чаще всего «материальная ценность» с полем «наименование». А отломанная ножка — «порча имущества» с полями «сумма» и «наименование», которая вообще не связана с конкретным стулом.
Поздравляю, вы наступили на иммутабельные грабли. Если я отломаю три ноги у стула, он перестанет быть стулом, поэтому «порча имущества» со стулом связана (иначе вам никогда не удастся определить стоимость порчи если всего порч было более двух).

Я не спорю, в принципе, что можно любую сущность описать в любой полной парадигме. Кому-то нравится оперировать понятиями «арендная единица» — я только за (это случится вне пределов моей команды, правда). Я же, с вашего позволения, продолжу называть стул — стулом, а не «арендной единицей».

Я в свое время напрямую спрашивал Вирдинга, почему в стандартной библиотеке эрланга нет того, что в эликсире называется «Agent». И он ответил: «GenServer» отлично справляется с этой ролью. И роль эта — хранить мутабельные объекты, даже если особым ревнителям чистой функциональности это не нравится.

iqiaqqivik > Я же, с вашего позволения, продолжу называть стул — стулом, а не «арендной единицей».
iqiaqqivik > Сидеть я на стульях не планирую, уточню на всякий случай.

Но ведь, позвольте возразить вам, — стул это то на чём сидят!
То есть, если я вас приглашу в гости, укажу перстом на стоящий в углу стул, и спрошу: «что это?» — то вы сначала зададите мне десять наводящих вопросов, прежде чем признаете, что это стул?

Кроме того, гостям я позволяю сидеть на сломанных стульях: мне не жалко.
iqiaqqivik > Кроме того, гостям я позволяю сидеть на сломанных стульях: мне не жалко.

Как у вас много проблем то появляется. — А надо было всего ничего — не ломать ножки у стула и вопросов бы и не было!
О том и речь была вначале.
Без доказательств принимаются только религиозные аргументы. Мне прекрасно известны доказательства того, что чистые функции хороши. Автор сего опуса не утрудил себя их приведением, именно поэтому к нему отношение как к очередному недалекому адепту. Если бы привел — отношение было бы чуть лучше, примерно как к капитану очевидность. Однако ни первый, ни второй случай не тянут на статью.
Являются ли две приведённые функции чистыми и иммутабельными?

public Int32 GetValue1()
{
    return 1;
}

public Int32 GetValue2()
{
    Int32 result = 2;
    return result;
}
define «иммутабельная функция»

Чистыми — являются.
Значит можно писать вместо:

render: function() {
        return  (<Form></Form>)
}


вот так:

render: function() {
        let result = '<Form></Form>';
        return  result;
}


и оставаться при этом чистым и иммутабельным?
НЛО прилетело и опубликовало эту надпись здесь
Разница огромная. В первом случае я не могу понять что конкретно возвращает моя функция, какого оно типа и существует ли вообще. Во втором, я всегда могу увидеть результат перед возвратом, поставить точку останова, подебажить. Сравните:

render: function() {
    return (
      <p>
        Hello, <input type="text" placeholder="Your name here" />
      </p>
    );
  }


и например так:

render: function() {
    let htmlInput = '<input type="text" placeholder="Your name here" />';
    let htmlContent = 'Hello, ' + htmlInput;
    let htmlResult = '<p>' + htmlContent + '</p>'
    return htmlResult;
  }


Мой вопрос, остаюсь ли я при этом верным концепту чистоты и иммутабельности?
НЛО прилетело и опубликовало эту надпись здесь
Вы глумитесь над вопросами, а между прочем об этом нигде не сказано и никак не обсуждается. Везде пропагандируют многостраничный функциональный код, вложенный друг в друга до безумия, начинающийся со слова return. И никто не может подтвердить что можно спокойно по шагам императивно писать иммутабельный код на чистых функциях.
НЛО прилетело и опубликовало эту надпись здесь
Пользуясь выданным вами правом самому отвечать на свои вопросы про мой код и всё же надеясь на аргументированную дискуссию, отвечаю:

код
render: function() {
    let htmlInput = '<input type="text" placeholder="Your name here" />';
    let htmlContent = 'Hello, ' + htmlInput;
    let htmlResult = '<p>' + htmlContent + '</p>';
    return htmlResult;
  }


является императивным и иммутабельной чистой функцией одновременно
На данный момент единственное упоминание «иммутабельных функций» в гугле — этот тред. Может всё таки расскажите о чём вы?
Я хочу понять почему требуется обязательно писать вперед ретурном? Иммутабельность как невозможность изменения состояния объекта после создания это очень хороший принцип, но почему при этом я не могу использовать переменные? Согласен, иммутабельная функция плохо звучит. Скажем так, функция которая для того же набора входных значений она возвращает одинаковый результат (чистая) может или не может внутри себя объявлять переменные и быть императивной?
Может. Внутри себя она может хоть интеграл пятого порядка считать. Главное — она не лезет в глобальный контекст и на одинаковые входные данные всегда реагирует одинаково (возвращаемое значение зависит только от аргументов функции).

Что касается переменных — несколько неявных переменных неизбежно объявляются. Конкретно в js это, как минимум, this, arguments, так же совсем внутри есть аналог переменной result и прочее. Так что сам по себе вопрос некорректный, потому что без переменных это не реализуется в принципе. Другой вопрос, что все переменные могут быть иммутабельными, но опять же, совсем совсем далеко внутри неявные мутабельные переменные будут.
Пока переменные только локальные — используйте сколько влезет.
Добрый день!

Я бы хотел ответить на ваш вопрос, потому что на него никто толком не ответил в тот раз.

Действительно, функция является чистой, когда ее аргументы — это все, что ей нужно для вычисления результата. Она не может никуда больше лазать (делать сайд-эффекты), кроме других чистых функций.

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

Сами эти внутренние переменные называются иначе — локализованное состояние (терминология может отличаться). Это состояние, которое за пределы функции не выходит. В Haskell, например, есть монада ST, которую можно использовать внутри чистых вычислений, но по факту она является императивным состоянием.

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

Еще добавлю, что в Haskell есть так называемые let-биндинги, которые выглядят императивно, но которые на самом деле сахарят вызов функций в виде выражений:

myFunc a b = let
c = a + 1
d = b + 1
e = c + d
in e + 10

Это эквивалентно:
myFunc a b = ((a + 1) + (b + 1)) + 10

Функциональное программирование значительно шире. Оно крутится, на самом деле, вокруг композиции функций, а иммутабельность и чистота этих функций — необходимые условия для композиции.
НЛО прилетело и опубликовало эту надпись здесь
Таким образом, вы подтвердаете мое предположение, что внутри чистых функций можно писать в императивном стиле?
НЛО прилетело и опубликовало эту надпись здесь
В вашем ответе чувствуется, что внутренне вы несогласны со мной, но предпочитаете сказать «ой всё»
Да, можете. Чистые функции и иммутабельные данные лишь часть парадигмы функционального программирования. Дело в том, что в данном случае вы используете часть парадигмы — чистые функции, но при этом внутри функции императивны. Ваш вопрос должен быть уровнем выше — является ли код полностью функциональным, и тогда если внутри функции есть изменения локальных состояний, ответ будет нет.
вы используете часть парадигмы — чистые функции

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

поддерживаю. Единственное чего я не могу понять из этого обсуждения — откуда взялся термин чистая функция? Всю дорогу (с момента когда я познакомился с программированием) — функция в программировании употреблялась в математическом смысле — и то что она при одних и тех же входных параметрах возвращает одни и те же значения — было вроде как не классифицирующим признаком а следствием. А разделяли функции и процедуры по возврату значения и доступу к environment — если возвращает результат и работа идет только с локальными переменными — функция, все остальное — процедуры. Вроде так было, откуда появились чистые и грязные функции?

Это знаете, как если женщина с причандалами, то она мужчина, а если мужчина без — он женщина. Вот так и тут. Функция с сайд-эффектами — грязная процедура, функция без — девствственная и непорочная чистая функциональная функция.
В программировании функции и процедуры — это прежде всего подпрограммы, обычно разделение проходит по тому есть ли возвращаемое значение или нет. Мейнстрим языки если и разделяют (Си, например, не разделяет, там всё функции, Ассемблер (intel-like как минимум) тоже не разделяет — там всё процедуры) функции и процедуры, то только по этому признаку, запрета на побочные действия нет в функциях (а в процедурах практически никогда и нет надобности без побочных эффектов).
Кажется, вы путаетесь в понятиях. Иммутабельность не является несовместимой с императивностью, так же как и чистота функции таковой не является. Так или иначе, возвращаясь к исходному вашему комментарию — не понимаю, что именно вы хотите донести.
Ваш код с SSA чисто функционален, чище некуда, и переписывается на пуританский хаскель один-в-один. Вот если бы в нём переиспользовалась одна и та же переменная вместо трёх, ещё можно задуматься.
> Вот если бы в нём переиспользовалась одна и та же переменная вместо трёх, ещё можно задуматься

Почему нельзя использовать одну локальную переменную несколько раз? Кто запретил? Чем чревато?
Как минимум ухудшением читаемости.
Нарушением иммутабельности состояния. Первое присвоение это _не_ изменение состояния, это его _создание_. Грубо говоря, все ваши переменные должны быть const, выглядеть как const и вести себя как const. Как в математике :)
> Первое присвоение это _не_ изменение состояния, это его _создание_

Ну второй раз тоже напишите var перед переменной. Будет создание. В Rust так можно

А вот в Erlang, например, нельзя. И он не слишком популярен в том числе и по этой причине

PS: для полного погружения в ФП надо заставить апологетов редукс заменить все for на соответствующие рекурсивные вызовы. Посмотрим как долго они протянут
> он не слишком популярен в том числе и по этой причине

Да, это ключевая причина непопулярности Эрланга, прямо в яблочко.
PS: для полного погружения в ФП надо заставить апологетов редукс заменить все for на соответствующие рекурсивные вызовы. Посмотрим как долго они протянут


Вот оно что! — На двух стульях думают усидеть!?
Ну второй раз тоже напишите var перед переменной. Будет создание. В Rust так можно

Ну тогда это опять-таки будут три разные переменные с одинаковыми именами.
Почему нельзя использовать одну локальную переменную несколько раз? Кто запретил? Чем чревато?

Можно, но код будет чисто функциональным, только если это переиспользование переменной нельзя пронаблюдать снаружи функции. Например, как в хаскельной монаде ST, или в линейных типах Clean.
НЛО прилетело и опубликовало эту надпись здесь
Это чистая функция. Ее результат определяется только ее входными параметрами.
Иммутабельность к вашим примерам не имеет никакого отношения.

иммутабельные структуры нужны там где они нужны. если у вас state меняется с нескольких источников (нет одного холдера мастер-данных) — то какие тут могут быть иммутабельные структуры? Нет же, лепят, модно же. А потом сидишь и разгребаешь — где данные не обновились, у кого старая копия зависла.

По моему опыту такие баги возникают как раз от развесистого всепроникающего mutable state.

Не соглашусь, от всепроникающего mutable state — баги скорее типа «кто-то где-то обновил, но непонятно кто и откуда» :) Т.е. грубо говоря, в первом случае проблема в рассинхронизации состояний (которые каждое само по себе консистентно), а во втором — в неконсистентности (но состояние одно и оно синхронизовано).
Добавлю, что redux, например, феноменально сложный для такого небольшого количества кода.

Реакт сам по себе простой. Фичи для хранения стейта из clojurescript(не интересовался чистым JS, увы) — атомы из clojure.core, курсоры из om, datascript — простые и понятные.
Redux — невыносимо непонятный, и почему его все так форсят для меня загадка. Непонятнее его для меня только cycle.js, наверное.
А что есть понятнее для Реакта?
Все перечисленное из мира clojurescript нормально работает с реактом. Om — react-based framework, атомы — это абсолютно библиотечная фича для синхроницированного доступа к данным, datascript — это библиотека предоставляющая упрощенную in-memory версию известной в узких кругах БД Datomic.
Итого, один фреймворк поверх реакта и две библиотеки, которые ничто не мешает с ним использовать.

Для JS есть куча разного качества реализаций Flux, но в детали я не вдавался. На redux я посмотрел на волне хайпа, понял, что ничего не понимаю и не очень хочется, и закрыл этот вопрос для себя.

Думаю, Redux — это облегчение для тех, кто ранее работал с другой реализацией Flux. От этого и популярность. Да и что может больше подойти для React чем Flux/Redux? Уже в других постах спрашивал об этом, пока не нашлось хорошего ответа.

Добавлю, что redux, например, феноменально сложный для такого небольшого количества кода.

Каждому своё, конечно… А мне вот наоборот, было куда сложнее вначале разобраться с React. С Flux/Redux же разобрался за день и ещё через пару дней освоил что-то вроде «best practices», после чего жизнь стала ещё легче и проще.
mobx
Да, это реальный конкурент Redux.
Статью порекомендовал на перевод я :) Что мне в ней понравилось? Идея того, что технологии — фигня. Они получают популярность, когда на них реализовывают крутые штуки. Пришли рельсы с DSL и скаффолдингом — об этих технологиях заговорили. Скаффолдинг много куда заимствовали. Пришел реакт с иммутабельным состоянием — «фанатики ооп лезут из всех щелей». Удобные и полезные штуки, ИМХО, первичны. И я рад, что авторы постоянно экспериментируют и пробуют новые подходы. Hot module replacement мне очень нравится — надеюсь, оно много куда заимствуется.

Возросший интерес к ФП — это аллергическая реакция на недавно прошедший всплеск ООП головного мозга в JavaScript.
Согласитесь, когда человек на языке, который позволяет запросто объявить объект, эмулирует класс, чтобы отнаследоваться от него и сэмулировать синглтон — это за гранью добра и зла. Такие чудеса уже кончились и такие чудаки большей частью перевелись, но реакция у сообщества, видимо, замедленная.

Сегодня синглтон, а завтра уже фабрика объектов.
Я может, просто не хочу путаться, и всегда завожу классы, и если уж так вышло, что объект мне нужен только один — я его создам через new один раз. Зато буду спокоен, что в случае чего смогу создать их еще 10, если потребуется.
if(x === 0)
return;
— плохо, лучше ведь заранее написать
if(x === 0)
{
return;
}
и жить спокойно.
Я к чему все это… ужасы случаются, но завести заранее класс вместо прямого определения объекта — не страшно.

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

Можно пример синглтона, который точно точно точно никогда никогда не станет классом?
Вот вам пример из недавней жизни. На изоморфном сайте есть загрузчик, который грузит первую страницу сайта. Сделаю его объектом в js, подумал я. Сделал, и понял что не могу сразу извлекать элементы через jQuery, потому что страница еще не загружена. document.ready? нет, загрузчик обязан появиться до того, как будет загружен html, вдруг страница — простыня на 2mb. Хорошо, значит нужно инициализировать загрузчик после его html представления. Что же, заведем функцию init? Но зачем мне лишняя сущность init, если я могу сделать класс, и после определения html элементов загрузчика вызвать var preloader = new Preloader. И все сразу хорошо, несмотря на то что у нас по идее preloader — типичный синглтон.

Обсуждали же уже много раз, кесарю кесарево. Ни ФП/иммутабельность, ни ООП не являются серебряной пулей per se.


Time-travel debugging это прикольно, конечно. Примерно как трюк на велосипеде, клево выглядит на ютубе, но вечером ты просто крутишь педали, чтобы попасть домой. Так и с кодом, ты просто хочешь чтоб твой коллега исправил баг до того, как он попадет на прод, и настраиваешь CI и пишешь тесты. Которые, если у тебя действительно чистые функции, можно прогонять в nodejs, не запуская громоздкий браузер в тормозной обвязке (да-да, Karma, я смотрю на тебя).

НЛО прилетело и опубликовало эту надпись здесь

Есть прекрасный tape-run, но все равно без браузера быстрее.

НЛО прилетело и опубликовало эту надпись здесь

Собственно, никто не мешает использовать тот же подход во втором ангуляре.

НЛО прилетело и опубликовало эту надпись здесь

А вы заканчивали?

НЛО прилетело и опубликовало эту надпись здесь
Сижу, смотрю на весь этот дикий оверхед, связанный с неизменяемыми состояниями и уже неудивляюсь, почему Chrome жрет по 200 Мб на вкладку, почему все программы работают все тормознее и тормознее…

Я глубоко убежден, что несмотря на всю математическую красоту и плюшки в многопоточности — функциональное программирование, охренеть какой ад для клиентских машин. А вы на нем хотите весь front-end писать.
Хуже всего — сборка мусора, которая тормозит не только одни функциональные, но и императивные программы тоже.
> функциональное программирование, охренеть какой ад для клиентских машин

Армстронг и Вирдинг с недоумением смотрят на этот тезис.
Поясните, пожалуйста.
Всегда вроде считалось, что эрланг (два чувака из моего комментария — его создатели) помимо прочего как раз очень _нетребователен к ресурсам_.
Спасибо! Теперь, понял о чём речь.
Такая маленькая (и почти бессмысленная банальная) статья, и так много комментариев :)
Это говорит о том, что тема ФП то горячая, по крайней мере в JS.
Мне кажется, что ФП просто само по себе интересно.
Программирование, которое ближе к искусству, чем к рутине.
Disclaimer: это чисто моё впечатление, не хочу никакого холивара, на ООП написал тоже много :)
Мне кажется, что ФП просто само по себе интересно.


Оно новее. Для масс. — Не то что идеи там новые — им 100 лет в обед — но ООП в своё время «выжег территорию». — Сейчас пошёл отскок.
А почему в редаксе не юзают клевое функциональное программирование, а используют грязную, бажную и недостойную процедурщину?

  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }
Зарегистрируйтесь на Хабре , чтобы оставить комментарий