Комментарии 88
Не успело закончиться введение, как начались выводы. Необычно.
Кхм.
чистые функции и иммутабельные структуры это круто
а что, нужны доказательства?
Это не значит, что чистые функции и иммутабельные структуры это плохо. Но это достаточно веский аргумент, чтобы сказать «чистые функции и иммутабельные структуры — это по-разному». Микроскоп, несомненно, более изысканный инструмент, но гвозди лучше забивать утюгом.
Почему это? Это ниоткуда не следует. А может быть, хочу, но учтите, что я пользуюсь стульями исключительно как дровами для печки. Или еще что.
Любая фраза про любую парадигму, если она содержит слово «всегда», не является верной. Иммутабельность — это очень хорошо. Иногда. А иногда — нет.
Ну раз вы не умеете мыслить абстрактно, вот вам абсолютно легитимный семантически пример: я арендую квартиру со стульями. Стулья — субстанция хрупкая, иногда ломаются. Покупка нового стула взамен старого — акт, требующий согласования с хозяевами. За каждую же отломанную ножку с меня просто вычтут 10 рублей, пока количество ног у стула более двух.
Сидеть я на стульях не планирую, уточню на всякий случай.
Я не спорю, в принципе, что можно любую сущность описать в любой полной парадигме. Кому-то нравится оперировать понятиями «арендная единица» — я только за (это случится вне пределов моей команды, правда). Я же, с вашего позволения, продолжу называть стул — стулом, а не «арендной единицей».
Я в свое время напрямую спрашивал Вирдинга, почему в стандартной библиотеке эрланга нет того, что в эликсире называется «Agent». И он ответил: «GenServer» отлично справляется с этой ролью. И роль эта — хранить мутабельные объекты, даже если особым ревнителям чистой функциональности это не нравится.
iqiaqqivik > Сидеть я на стульях не планирую, уточню на всякий случай.
Но ведь, позвольте возразить вам, — стул это то на чём сидят!
Кроме того, гостям я позволяю сидеть на сломанных стульях: мне не жалко.
Как у вас много проблем то появляется. — А надо было всего ничего — не ломать ножки у стула и вопросов бы и не было!
О том и речь была вначале.
public Int32 GetValue1()
{
return 1;
}
public Int32 GetValue2()
{
Int32 result = 2;
return result;
}
Чистыми — являются.
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;
}
Мой вопрос, остаюсь ли я при этом верным концепту чистоты и иммутабельности?
код
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 — если возвращает результат и работа идет только с локальными переменными — функция, все остальное — процедуры. Вроде так было, откуда появились чистые и грязные функции?
Почему нельзя использовать одну локальную переменную несколько раз? Кто запретил? Чем чревато?
Ну второй раз тоже напишите var перед переменной. Будет создание. В Rust так можно
А вот в Erlang, например, нельзя. И он не слишком популярен в том числе и по этой причине
PS: для полного погружения в ФП надо заставить апологетов редукс заменить все for на соответствующие рекурсивные вызовы. Посмотрим как долго они протянут
Да, это ключевая причина непопулярности Эрланга, прямо в яблочко.
PS: для полного погружения в ФП надо заставить апологетов редукс заменить все for на соответствующие рекурсивные вызовы. Посмотрим как долго они протянут
Вот оно что! — На двух стульях думают усидеть!?
Ну второй раз тоже напишите var перед переменной. Будет создание. В Rust так можно
Ну тогда это опять-таки будут три разные переменные с одинаковыми именами.
Почему нельзя использовать одну локальную переменную несколько раз? Кто запретил? Чем чревато?
Можно, но код будет чисто функциональным, только если это переиспользование переменной нельзя пронаблюдать снаружи функции. Например, как в хаскельной монаде ST, или в линейных типах Clean.
Иммутабельность к вашим примерам не имеет никакого отношения.
иммутабельные структуры нужны там где они нужны. если у вас state меняется с нескольких источников (нет одного холдера мастер-данных) — то какие тут могут быть иммутабельные структуры? Нет же, лепят, модно же. А потом сидишь и разгребаешь — где данные не обновились, у кого старая копия зависла.
По моему опыту такие баги возникают как раз от развесистого всепроникающего mutable state.
Реакт сам по себе простой. Фичи для хранения стейта из clojurescript(не интересовался чистым JS, увы) — атомы из clojure.core, курсоры из om, datascript — простые и понятные.
Redux — невыносимо непонятный, и почему его все так форсят для меня загадка. Непонятнее его для меня только cycle.js, наверное.
Итого, один фреймворк поверх реакта и две библиотеки, которые ничто не мешает с ним использовать.
Для JS есть куча разного качества реализаций Flux, но в детали я не вдавался. На redux я посмотрел на волне хайпа, понял, что ничего не понимаю и не очень хочется, и закрыл этот вопрос для себя.
Добавлю, что redux, например, феноменально сложный для такого небольшого количества кода.
Каждому своё, конечно… А мне вот наоборот, было куда сложнее вначале разобраться с React. С Flux/Redux же разобрался за день и ещё через пару дней освоил что-то вроде «best practices», после чего жизнь стала ещё легче и проще.
-
Возросший интерес к ФП — это аллергическая реакция на недавно прошедший всплеск ООП головного мозга в 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, я смотрю на тебя).
Собственно, никто не мешает использовать тот же подход во втором ангуляре.
Я глубоко убежден, что несмотря на всю математическую красоту и плюшки в многопоточности — функциональное программирование, охренеть какой ад для клиентских машин. А вы на нем хотите весь front-end писать.
Армстронг и Вирдинг с недоумением смотрят на этот тезис.
Программирование, которое ближе к искусству, чем к рутине.
Disclaimer: это чисто моё впечатление, не хочу никакого холивара, на ООП написал тоже много :)
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)
}
}
JavaScript в 2016 году: функциональное программирование пришло всерьез и надолго