1. Первые шаги
2. Сочетаем функции
3. Частичное применение (каррирование)
4. Декларативное программирование
5. Бесточечная нотация
6. Неизменяемость и объекты
7. Неизменяемость и массивы
8. Линзы
9. Заключение
Данный пост является четвёртой частью серии о функциональном програмировании под названием «Мышление в стиле Ramda».
В третьей части мы говорили об объединении функций, которые могут принимать больше одного аргумента, используя техники частичного применения и каррирования.
Когда мы начинаем писать маленькие функциональные строительные блоки и объединять их, мы обнаруживаем, что нам необходимо написать множество функций, которые будут оборачивать операторы JavaScript, такие как арифметика, сравнение, логика и управление потоком. Это может показаться утомительным, но мы находимся за спиной Ramda.
Но сначала, небольшое введение.
Есть множество различных путей для разделения языков программирования и стилей написания. Это статическая типизация против динамической типизаци, интерпретируемые языки и компилируемые языки, высокоуровневые и низкоуровные, и так далее.
Другое подобное разделение заключается в императивном програмировании против декларативного.
Без погружения вглубь этого, императивное программирование — это стиль программирования, в котором программисты говорят компьютеру, что нужно сделать, объясняя ему, как это нужно сделать. Императивное программирование даёт множество конструкций, которые мы используем каждый день: управление потоком (
Декларативное программирование — это стиль програмирования, в котором програмисты говорят компьютеру, что нужно сделать, объясняя ему, что они хотят. Компьютер далее должен определить, как получить необходимый результат.
Один из классических декларативных языков — это Prolog. В Prolog програма состоит из набора фактов и набора правил вывода. Вы начинаете программу, задавая вопрос, и набор правил вывода Prolog'а использует факты и правила для ответа на ваш вопрос.
Функциональное программирование рассматривается как подмножество декларативного програмирования. В функциональной программе, мы объявляем функции и далее объясняем компьютеру что мы хотим сделать, совмещая данные функции.
Даже в декларативных программах необходимо выполнять подобные задачи, которые мы выполняем в императивных программах. Управление потоком, арифметика, сравнения и логика всё ещё являются базовыми строительными блоками, с которыми мы должны работать. Но нам необходимо найти способы для выражения этих конструкций в декларативном стиле.
Поскольку мы программируем на JavaScript, императивном языке, это нормально — использовать стандартные императивные конструкции при написании «нормального» JavaScript кода.
Но когда мы пишем функциональные трансформации, используя конвееры и подобные им конструкции, императивные конструкции перестают вписываться с создаваемую структуру кода.
Посмотрим на некоторые базовые строительные блоки, которые предоставляет Ramda для того чтобы помочь нам выйти из этой неприятной ситуации.
Во второй части мы реализовали серию арифметических трансформаций для демонстрации конвеера:
Обратите внимание, как мы пишем функции для всех базовых строительных блоков, которые мы желаем использовать.
Рамда предоставляет функции add, subtract, multiply и divide для использования в местах стандартных арифметических операций. Так что мы можем использовать рамдовскую
Мы можем использовать
Так что мы можем ещё немного упростить наш конвеер:
Также во второй части мы написали несколько функций для определения, является ли персона имеющей право на голосование. Конечная версия того кода выглядела следующим образом:
Обратите внимание, что некоторые из наших функций использут стандартные операторы сравнения (
Давайте преобразуем наш код на использование equals вместо
Ramda также предоставляет gt для
Обратите внимание, что эти функции, как кажется, принимают свои аргументы в нормальном порядке (первый аргумент больше второго?). Это имеет смысл, когда мы используем их в изоляции, но может сбивать с толку при объединении функций. Эти функции нарушают принцип «данные идут последними», так что нам нужно быть осторожными, когда мы используем иих в наших конвеерах и подобных им ситуациях. И это место, когда flip и заполнитель (__) становятся полезными.
В дополнение к
Существует набор случаев основных применений для
Во второй части (и чуть выше), мы использовали функции
Эти комбинированные функции работают прекрасно, когда функции объединяют операцию над тем же значением. Написанные выше
Но иногда нам нужно применить
В основном,
Это распространённая идиома, и чаще всего работающая, но полагающаяся на JavaScript логику определения «ложности». Что если
Мы можем использовать функцию
Управление потоком выполнения менее важно в функциональном программировании, но иногда оказывается нужным. Коллекция итерирующих функций, о которых мы говорили в первой части, заботиться о большинстве ситуаций с циклами, но условия всё ещё довольно важны.
Давайте напишем функцию,
Обратите внимание, что наше условие (
Теперь мы на позиции, когда мы можем использовать функцию ifElse из Ramda, которая является эквивалентом структуры
Как мы упомянули выше, функции сравнения не работают подобно функциям объединения, так что здесь нам нужно начать использовать заполнитель (
В данном случае, мы должны читать это как «21 меньше или равно
Функции-константы весьма полезны в ситуациях, подобных этой. Как вы можете предположить, Ramda предоставляет нам сокращение. В данном случае, сокращение называется always.
Ramda также предоставляет T и F в качестве дальнейших сокращений для
Давайте попробуем написать другую функцию,
Вторая ветвь сравнения (
Как вы уже можете ожидать, Ramda предоставляет нам функцию identity:
Выражение
Если, как в нашем случае, вторая ветвь является тождественностью, мы можем использовать when вместо
Если первая ветвь условия является тождественностью, мы можем использовать unless. Если мы перевернём наше условие на использование
Ramda также предоставляет функцию cond, которая может заменить выражение
Мне не понадобилось использовать
Мы рассмотрели набор функций, которые Ramda предоставляет нам для превращения нашего императивного кода в декларативный функциональный код.
Вы могли заметить, что последние несколько функций, которые мы написали (
Это распространённый паттерн, и вновь Ramda предоставляем нам инструменты для того чтобы привести всё это к более чистому виду. Следующий пост, «Бесточечная нотация» рассматривает способы, позволяющие упростить функции, следующие подобному паттерну.
2. Сочетаем функции
3. Частичное применение (каррирование)
4. Декларативное программирование
5. Бесточечная нотация
6. Неизменяемость и объекты
7. Неизменяемость и массивы
8. Линзы
9. Заключение
Данный пост является четвёртой частью серии о функциональном програмировании под названием «Мышление в стиле Ramda».
В третьей части мы говорили об объединении функций, которые могут принимать больше одного аргумента, используя техники частичного применения и каррирования.
Когда мы начинаем писать маленькие функциональные строительные блоки и объединять их, мы обнаруживаем, что нам необходимо написать множество функций, которые будут оборачивать операторы JavaScript, такие как арифметика, сравнение, логика и управление потоком. Это может показаться утомительным, но мы находимся за спиной Ramda.
Но сначала, небольшое введение.
Императивность vs Декларативность
Есть множество различных путей для разделения языков программирования и стилей написания. Это статическая типизация против динамической типизаци, интерпретируемые языки и компилируемые языки, высокоуровневые и низкоуровные, и так далее.
Другое подобное разделение заключается в императивном програмировании против декларативного.
Без погружения вглубь этого, императивное программирование — это стиль программирования, в котором программисты говорят компьютеру, что нужно сделать, объясняя ему, как это нужно сделать. Императивное программирование даёт множество конструкций, которые мы используем каждый день: управление потоком (
if
-then
-else
синтаксис и циклы), арифметические операторы (+
, -
, *
, /
), операторы сравнения (===
, >
, <
, и т.д.), и логические операторы (&&
, ||
, !
).Декларативное программирование — это стиль програмирования, в котором програмисты говорят компьютеру, что нужно сделать, объясняя ему, что они хотят. Компьютер далее должен определить, как получить необходимый результат.
Один из классических декларативных языков — это Prolog. В Prolog програма состоит из набора фактов и набора правил вывода. Вы начинаете программу, задавая вопрос, и набор правил вывода Prolog'а использует факты и правила для ответа на ваш вопрос.
Функциональное программирование рассматривается как подмножество декларативного програмирования. В функциональной программе, мы объявляем функции и далее объясняем компьютеру что мы хотим сделать, совмещая данные функции.
Даже в декларативных программах необходимо выполнять подобные задачи, которые мы выполняем в императивных программах. Управление потоком, арифметика, сравнения и логика всё ещё являются базовыми строительными блоками, с которыми мы должны работать. Но нам необходимо найти способы для выражения этих конструкций в декларативном стиле.
Декларативные заменители
Поскольку мы программируем на JavaScript, императивном языке, это нормально — использовать стандартные императивные конструкции при написании «нормального» JavaScript кода.
Но когда мы пишем функциональные трансформации, используя конвееры и подобные им конструкции, императивные конструкции перестают вписываться с создаваемую структуру кода.
Посмотрим на некоторые базовые строительные блоки, которые предоставляет Ramda для того чтобы помочь нам выйти из этой неприятной ситуации.
Арифметика
Во второй части мы реализовали серию арифметических трансформаций для демонстрации конвеера:
const multiply = (a, b) => a * b
const addOne = x => x + 1
const square = x => x * x
const operate = pipe(
multiply,
addOne,
square
)
operate(3, 4) // => ((3 * 4) + 1)^2 => (12 + 1)^2 => 13^2 => 169
Обратите внимание, как мы пишем функции для всех базовых строительных блоков, которые мы желаем использовать.
Рамда предоставляет функции add, subtract, multiply и divide для использования в местах стандартных арифметических операций. Так что мы можем использовать рамдовскую
multiply
там, где мы использовали самописную функцию, мы можем взять преимущество каррированной функции add
для замены нашей addOne
, и мы также можем написать square
с помощью multiply
.const square = x => multiply(x, x)
const operate = pipe(
multiply,
add(1),
square
)
add(1)
очень похожа на оператор инкрементирования (++
), но оператор инкрементирования изменяет переменную, так что он вызывает мутацию. Как мы узнали из первой части, иммутабельность — это основной принцип функционального программирования, так что мы не хотим использовать ++
или его кузена --
.Мы можем использовать
add(1)
и subtract(1)
для увеличения и уменьшения, но так как эти две операции такие распространённые, Ramda предоставляет inc и dec вместо них.Так что мы можем ещё немного упростить наш конвеер:
const square = x => multiply(x, x)
const operate = pipe(
multiply,
inc,
square
)
subtract
является заменой бинарного оператора -
, но у нас ещё имеется унарный оператор -
для отрицания значения. Мы также можем использовать multiply(-1)
, но Ramda предоставляет функцию negate для выполнения этой задачи.Сравнение
Также во второй части мы написали несколько функций для определения, является ли персона имеющей право на голосование. Конечная версия того кода выглядела следующим образом:
const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18
const isCitizen = either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = both(isOver18, isCitizen)
Обратите внимание, что некоторые из наших функций использут стандартные операторы сравнения (
===
и >=
в данном случае). Как вы можете предположить сейчас, Ramda также предоставляет заменители для всего этого.Давайте преобразуем наш код на использование equals вместо
===
и gte вместо >=
.const wasBornInCountry = person => equals(person.birthCountry, OUR_COUNTRY)
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => gte(person.age, 18)
const isCitizen = either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = both(isOver18, isCitizen)
Ramda также предоставляет gt для
>
, lt для <
и lte для <=
Обратите внимание, что эти функции, как кажется, принимают свои аргументы в нормальном порядке (первый аргумент больше второго?). Это имеет смысл, когда мы используем их в изоляции, но может сбивать с толку при объединении функций. Эти функции нарушают принцип «данные идут последними», так что нам нужно быть осторожными, когда мы используем иих в наших конвеерах и подобных им ситуациях. И это место, когда flip и заполнитель (__) становятся полезными.
В дополнение к
equals
есть ещё identical для определения, являются ли два значения ссылками на то же пространство в памяти.Существует набор случаев основных применений для
===
: проверка, что строка или массив являются пустыми (str === ''
или arr.length === 0
) и проверка, является ли переменная равной null
или undefined
. Ramda предоставляет удобные функции для обоих случаев: isEmpty и isNil.Логика
Во второй части (и чуть выше), мы использовали функции
both
и either
в местах операторов &&
и ||
. Мы также говорили о complement
для мест с !
.Эти комбинированные функции работают прекрасно, когда функции объединяют операцию над тем же значением. Написанные выше
wasBornInCountry
, wasNaturalized
и isOver18
все применялись к объекту персоны.Но иногда нам нужно применить
&&
, ||
и !
к различным значениям. Для подбоных случаев Ramda предоставляет нам функции and, or и not. Я думаю следующим образом: and
, or
и not
работают со значениями, в то время как both
, either
и complement
работают с функциями.В основном,
||
используется для получения значений по умолчанию. К примеру, мы можем написать что-нибудь вроде этого:const lineWidth = settings.lineWidth || 80
Это распространённая идиома, и чаще всего работающая, но полагающаяся на JavaScript логику определения «ложности». Что если
0
является валидным параметром? Так как 0
является ложным значением, мы получим значение линии равное 80.Мы можем использовать функцию
isNil
, о которой мы только что узнали выше, но Ramda снова имеет более логичный вариант для нас: defaultTo.const lineWidth = defaultTo(80, settings.lineWidth)
defaultTo
проверяет второй аргумент на isNil
. Если проверка провалилась он вернёт полученное значение, в ином случае вернёт первый аргумент, переданный ей.Условия
Управление потоком выполнения менее важно в функциональном программировании, но иногда оказывается нужным. Коллекция итерирующих функций, о которых мы говорили в первой части, заботиться о большинстве ситуаций с циклами, но условия всё ещё довольно важны.
ifElse
Давайте напишем функцию,
forever21
, которая получает год и возвращает следующий. Но, как нам указывает её имя, начиная с 21 года, он будет оставаться в этом значении.const forever21 = age => age >= 21 ? 21 : age + 1
Обратите внимание, что наше условие (
age >= 21
) и вторая ветвь (age + 1
) могут быть обе написаны как функции age
. Мы можем переписать первую ветвь (21
) как функцию-константу (() => 21
). Теперь у нас будет три функции, которые принимают (или игнорируют) age
.Теперь мы на позиции, когда мы можем использовать функцию ifElse из Ramda, которая является эквивалентом структуры
if...then..else
или её более короткого кузена, тернарного оператора (?:
).const forever21 = age => ifElse(gte(__, 21), () => 21, inc)(age)
Как мы упомянули выше, функции сравнения не работают подобно функциям объединения, так что здесь нам нужно начать использовать заполнитель (
__
). Мы также можем применить lte
вместо этого:const forever21 = age => ifElse(lte(21), () => 21, inc)(age)
В данном случае, мы должны читать это как «21 меньше или равно
age
». Я собираюсь придерживаться версии с заменителем в оставшейся части поста, так как я нахожу это более читабельным и менее запутывающим.Константы
Функции-константы весьма полезны в ситуациях, подобных этой. Как вы можете предположить, Ramda предоставляет нам сокращение. В данном случае, сокращение называется always.
const forever21 = age => ifElse(gte(__, 21), always(21), inc)(age)
Ramda также предоставляет T и F в качестве дальнейших сокращений для
always(true)
и always(false)
Тождественность
Давайте попробуем написать другую функцию,
alwaysDrivingAge
. Эта функция принимает age
и возвращает его, если его значение gte
16. Если же оно меньше 16, то она вернёт 16. Это позволяет любому притвориться, что он управляет возрастом, даже если это не так:const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), a => a)(age)
Вторая ветвь сравнения (
a => a
) — это другой типичный паттерн в функциональном программировании. Это известно как «тождественность» (не знаю точного перевода термина «identity function», просто выберу этот — прим. пер.). То есть, это функция, которая просто возвращает тот аргумент, который она получила.Как вы уже можете ожидать, Ramda предоставляет нам функцию identity:
const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), identity)(age)
identity
может принять больше одного аргумента, но всегда вернёт только первый. Если мы хотим вернуть что-то другое, отличное от первого аргумента, для этого существует более общая функция nthArg. Это гораздо менее распространённая ситуация, чем использование identity
.«when» и «unless»
Выражение
ifElse
, в котором одна из логических ветвей является тождественностью, также является типичным патеррном, так что Ramda предоставляет нам больше сокращающих методов.Если, как в нашем случае, вторая ветвь является тождественностью, мы можем использовать when вместо
ifElse
:const alwaysDrivingAge = age => when(lt(__, 16), always(16))(age)
Если первая ветвь условия является тождественностью, мы можем использовать unless. Если мы перевернём наше условие на использование
gte(__, 16)
, мы можем использовать unless
.const alwaysDrivingAge = age => unless(gte(__, 16), always(16))(age)
Cond
Ramda также предоставляет функцию cond, которая может заменить выражение
switch
или цепочку выражений if...then...else
.const water = temperature => cond([
[equals(0), always('water freezes at 0°C')],
[equals(100), always('water boils at 100°C')],
[T, temp => `nothing special happens at ${temp}°C`]
])(temperature)
Мне не понадобилось использовать
cond
в моём коде с Ramda, но я писал подобный код на Lisp много лет назад, так что cond
чувствуется старым другом.Заключение
Мы рассмотрели набор функций, которые Ramda предоставляет нам для превращения нашего императивного кода в декларативный функциональный код.
Далее
Вы могли заметить, что последние несколько функций, которые мы написали (
forever21
, drivingAge
и water
) все принимают параметры, создают новую функцию и далее применяют эту функцию к параметру.Это распространённый паттерн, и вновь Ramda предоставляем нам инструменты для того чтобы привести всё это к более чистому виду. Следующий пост, «Бесточечная нотация» рассматривает способы, позволяющие упростить функции, следующие подобному паттерну.
Комментарии (33)
zim32
15.04.2018 22:40+3Еще один инструмент который делает тоже самое но необходимо его учить. В чем профит не пойму
Neiromaster
16.04.2018 14:13Много ошибок по тексту. Материал полезен, но перед публикацией хорошо бы вычитку проводить.
saggid Автор
16.04.2018 14:15Я честно его прошерстил пару раз и исправил всё, что заметил, но я такой же простой смертный, как и многие другие. Можете написать сюда или в личку свои замечания, два простых смертных всегда лучше одного. Воспринимайте это как помощь друг другу в полезном деле.
TheShock
16.04.2018 14:25+1Много ошибок по тексту
Такие вещи принято писать в личку. Хотите помочь — напишите туда ошибки, которые заметили. А просто замечание в воздух не имеет никакого смысла.
andreylartsev
17.04.2018 10:31Почему в качестве примера декларативных языков программирования всегда приводят Prolog? Хотя к нашим современным реалиям более близки HTML / CSS или SQL которые являются классическим декларативными языками программирования.
AngReload
Честно говоря, условия и сравнения в Ramda выглядят настолько запутывающими, что хочется использовать простые стрелочные функции вместо них.
saggid Автор
Мне кажется, что вы просто привыкли к императивной логике, и поэтому она кажется сейчас вам более простой, чем декларативная)
Сравните два этих примера:
Первый императивный, второй декларативный. Я чуток упростил второй пример, применив в нём "бесточечную нотацию", о которой рассказывается в следующей части серии, это не мешает ему оставаться рабочим.
Вот для простого человека, который программирование знает совсем мало, какой из вариантов выглядит более очевидным сейчас?) Думаю, это дело привычки в большей степени. Не стоит торопиться с выводами, наверное надо немного поработать с написанием подобного кода, чтобы начать воспринимать его как нечто привычное.
Zenitchik
Оба настолько просты, что могут считаться хоть декларативными, хоть императивными.
Но стрелочная функция не содержит внешних зависимостей.
saggid Автор
Я рад за неё. Сжатый код всей рамды весит 12 килобайт. Примерно в 2.5 раза меньше текущего jquery и в 2 раза меньше лодаша. Код современных проектов обычно в сотни раз объемнее. При этом, с использованием рамды, есть определенная вероятность, что по конечному объему размер кода проекта выйдет даже меньшим, в силу большого количества переиспользования одних и тех же строительных блоков-функций, так что это спорное утверждение изначально.
Riim
Мне стало интересно сравнить производительность приведённых примеров. Вариант на Ramda оказался примерно в 200 раз медленнее. Код:
Результат:
saggid Автор
Кто сомневался, что ванила быстрее библиотеки? Поэтому реакт и вью и проводят столько работ по оптимизации своего кода. Конечно на ваниле все в тысячи раз быстрее. Кроме скорости разработки конкретных фич в проектах)
TheShock
Ирония в том, что на Ramda очень весело писать — чувствуешь себя таким умным, модным, функциональным, твой код столько краток и декларативен!
А когда дело доходит до поддержки — даже фанаты Рамда боятся лезть в код, тем более чужой. Я уже два года даю ссылку на один пример:
habrahabr.ru/post/279715/#comment_8812695
Человек написал рамда-код и очень расхваливал фреймворк, но в коде закрался баг и, хотя я уже десять раз давал в разных спорах ссылку на этот комментарий — никто не смог его починить. Потому что код на Рамда совершенно неподдерживаемый. Он write-only
saggid Автор
Почитал, изучил тему, разобрался с кодом. Вообще, проблема конкретно в этом примере не связана с Ramda, она была связана с Rx.js. Пришлось немного изучить, как он работает и понять суть проблемы.
Вот исправленный вариант, работает так, как вы хотели, без "бага": https://jsfiddle.net/a9m50xev/29/
Мне хватило примерно получаса чтобы разобраться с проблемой. Естественно, функциональщина — это такая штука, с которой, наверное, сможет разобраться тот, у кого есть более-менее основательный опыт в программировании. Для меня лично логика вполне стала уловимой и ясной из вышенаписанного кода спустя где-то 20 минут изучения логики работы используемых функций, благо документация к ним имеется в открытых источниках.
TheShock
Скажите, вы правда считаете, что нормально то, что код в 20 строчек требует 20 минут на изучение, чтобы стать понятным человеку, который имеет, как вы сказали, более-менее основательный опыт в программировании? А что делать с кодом на 100 строк? С кодом на 1000 строк?
Признайтесь честно, сколько времени у вас заняло понять логику работы моего императивного кода?
saggid Автор
Да это же чисто вопрос того, насколько ты привык шпилить проекты на Rx.js и Ramda. Если бы я пилил на них уже пару лет — я бы понял там всё намного быстрее.
Я согласен, что в данном случае императивный вариант оказался вполне хорош.
Вышеуказанный пример функциональщины я лично не могу никак посчитать идеальным. В нём много чего путает, в силу того, как его написал сам автор: десяток анонимных функций уже сам по себе сбивает с толку. Был бы я тимлидом в проекте, увидел бы такую портянку — раскритиковал бы автора за это и заставил переписывать на нормальные именованные функции-блоки, имеющие ясную логику работы. Вы в своём примере примерно это же и сделали ведь. С этим кодом надо бы сделать то же самое, а сейчас он действительно выглядит и страшным, и запутанным, я с этим не спорю. Но запутан он не потому что там используется функциональщина, а потому что автор не стремился к ясности, видимо очень ему хотелось именно "понтануться" различными вывертами функциональными, что я лично никогда не ценил в людях.
Однако, даже несмотря на все эти недостатки, в нём можно разобраться, можно его отрефакторить, сделать более чистым и ясным. Ещё хорошо бы знать Rx.js на нормальном уровне, чтобы сделать это ещё более качественным образом. У меня возникает ощущение, что этот код писал не совсем опытный программист, хотя-бы та же связка с жиквери немного обескураживает.
И вообще в этом случае, по-моему, не надо было совсем использовать Rx.js, ибо он не для подобных вещей, он скорее для реакции на множественные потоки событий предназначем, как я знаю. Он здесь просто излишен, ни к чему. Переусложнение без необходимости.
Можно написать намного более простую реализацию с использованием только рамды и нативной подписки на DOM-события, этого было бы более чем достаточно.
А говнокод может быть и в императивном коде ведь. Я очень не уверен в данный момент, что всякую функциональщину можно назвать "говнокодом" априори, как вы это пытаетесь сделать.
TheShock
Я не говорю, что всякая функциональщина — говнокод. Я говорю, что:
1. В целом функциональщину в JS сложнее поддерживать
2. Большинство функциональщиков в JS считают себя офигенными экспертами, что пишут хороший и поддерживаемый код, а в итоге другие функциональщики должны потратить 20 минут на разбор 20 строк кода.
3. Практически любой неначинающий JS программист смог бы работать с моим кодом. Очень мало JS программистов смогли бы работать с кодом, на который я дал ссылку. Да и, уверен, с вашим кодом.
Так скажите — какие преимущества у использовании Ramda, если можно писать код, как у меня (вообще без библиотек), с теми же усилиями и получить значительно более легкую поддержку?
saggid Автор
Вы наверное и редукс в проектах не используете, потому что слишком сложно? Знаете, на проект, использующий редукс, новичок без опыта точно также будет смотреть как баран на новые ворота. Только вот большинство людей этого мира все-таки понимают бенефиты от редакса (но не все старпёры хабра, надо заметить). Реакт тоже может быть довольно сложной асинхронной хернёй в некоторых случаях в силу своего управления стейта, однако мир современный он завоевал. И проекты, которые пишут на реакте и редаксе — они в реальности намного более более простые для понимания, я лично по своему опыту вижу, чем те проекты, в которых применяется некая самописная логика, подобная вашей. Но для человека, который в реакте и редаксе не разбирается — это будет адом в каком-то смысле. Ибо ему совершенно будет непонятно, что и как устроено. До того момента пока он не почитает вдумчиво официальную документацию к инструментам. Точно также с рамдой. Я честно вообще не вижу здесь пространства для споров, хотя посетители хабра иногда меня конечно удивляют подобными вышеуказанными статьями. Уже половина мира использует редакс — но на хабре есть масса людей, которые считают, что редакс это вообще плохо… Как-бы нам в своём мирке таким макаром не остаться совсем, даже железного занавеса никакого не нужно, достаточно подобного вашему стремления подтасовать факты в своих измышлениях и успокоиться на этом, как на высокообъективных доказательствах.
Я наверное дальше не буду обсуждать это, извините. Для меня лично просто всё понятно в этом плане. Я не знаю что тут дальше привести, зачем тратить время на обсуждение этого. Если у вас другое мнение — я рад за то, что вы способны его иметь и высказывать. А спорить я не люблю.
TheShock
Вы ведь понимаете, что это тоже логическая ошибка, по аналогии с вашей ссылкой.
Я вот считаю, что Редакс стал таким популярным за счет пиара его автора, что можно быдлокодить не задумываясь и при этом подавать себя как крутого прогрессивного разработчика
А в подтверждение моих слов выходят статьи с таким бредом:
И потому Async/await — это плохо.
А еще подтверждение тот код, который я дал. Его автор искренне считал, что он — очень крутой, потому что функциональный. Он не думал о легкости поддержке, он думал он функциональности.
А теперь вы используете вторую логическую ошибку — подмену понятий. Я нигде не говорил, что реакт — плох. Наоборот, я считаю его — прекрасным инструментом, заслужено получившим свою славу.
Второй раз ту же логическую ошибку. Конечно плохо, а то, что есть люди, которые клюнули на этот маркетинг — далеко не показатель.
saggid Автор
Давайте я напишу вам один раз одно большое сообщение, подробно разъясняющее пару некоторых важных моментов, на которое впоследствии буду просто отправлять вас и подобных вам людей, чтобы не повторяться.
В ваших словах как всегда светится желание возвысить один инструмент до небес и доказать, что другой — это результат "быдлокодерства", то есть там вообще всё изначально неправильно, как я понимаю. С вашей точки зрения. Потому что функциональщина, и так далее.
Альтернативой redux вы назвали mobx. Хорошо хоть не ту mvc-приколюху из соседней статьи от ненавистника редакса, в которой вызывается forceUpdate на каждое изменение. Я посмотрел на mobx, вижу, что в основе идея практически та же самая: inject + observer вместо connect у редакса, классы-сторы, вместо экшенов и единого хранилища у редакса. Вижу что он более простой и изначально решает меньше проблем и предоставляет меньше возможностей, при этом работает он на основе внутренней магии, т.е. мы доверяемся заклинаниям разрабов mobx и должны молиться, что всё просто будет хорошо. Мне уже на этом моменте не слишком всё нравится, особенно после распиаренного "убийцы пыхи" Ruby, авторы которого столько сил потратили на пропаганду монки патчинга, из-за чего потом стали терять даже core-контрибьютеров больших проектов. Спасибо, я лично лучше на пхп останусь: явное лучше неявного.
Ну да вернёмся к mobx. Что мы теряем сразу, отбрасывая "быдлокодерский редакс"?
и возможность быстрой реализации отмены изменений в приложении
И это как минимум. Я просто глубоко не вдавался в различия. Ну да, зато "быдлокодерской функциональщины" у нас в проекте больше не будет. Да, можно ещё закинуть в mobx-проект плагин mobx-state-tree и получить в итоге практически тот же redux, но на заклинаниях, и после этого молиться, чтобы вся эта смесь не заглючила в один прекрасный день, когда нам неожиданно придётся разбираться в магии mobx'а. Но вообще, зачем было изначально уходить от редакса, если к нему всё равно в итоге приходим?
Давайте почитаем цитаты людей, сравнивающих mobx и redux. Я почитал три статьи (1,2,3) на эту тему. Я надеюсь, вы способны воспринимать другие мнения людей из этого мира, не может же быть, наверное, что вы на этом земном шаре — самый умный и знаете 100% больше других? Давайте приведём некоторые цитаты из вышеуказанных статей:
Я понял, что mobx — это интересная штука, спасибо за это. Но говорить, что redux — это говно, а mobx — это идеал — тоже неправильно как-бы. Как минимум, мы видим, что проблемы имеются в том числе и у mobx, а вероятность написать кучу говнокода, особенно если ты только начинаешь фронтенд-карьеру — намного выше. Соответственно, мы тут изначально обсуждали, где будет больше говнокода — получается, что говнокода всё-таки будет больше именно вместе с mobx, а не с redux'ом.
Забавно кстати, тот же автор редакса хвалит в том числе и mobx в своём же твиттере. Опять наглый пиар!.. Суда на них нет, одни пропагандисты-обманщики..
Как мы видим, как всегда нет в реальном мире "говёного редакса" и "идеального mobx'а". Всё гораздо более сглаженно, и хорошие стороны есть и у первого, и у второго решения. И в реальности почти всегда бывает именно так.
Что очень важно для меня лично, и надеюсь, что когда-нибудь станет важным и для вас в том числе: подобное поведение встречается в СНГ слишком часто. Мне трудно жить в таком обществе. В большинстве своём, люди не способны оценивать всё умеренно, спокойно и с уважением. Они постоянно перелетают из одной крайности в другую. От любви до ненависти один шаг, как говорится. Сегодня вы топите за mobx и императивный код, завтра "прозреете" и начнёте топить за какой-нибудь HypeX, а всё остальное называть "говнокодерством".
У вас может уже и было подобное ранее, а? Наверное PHP поунижать успели за "говнокодинг", наверное тоже уверены, что он никому нинужен. Правда Laravel вот нынче имеет больше звёзд на гитхабе, чем Rails и Django (42 146 vs 39 345 vs 33 221). Да и Facebook, Vk, Wikipedia, Badoo, и прочие спокойно применяют PHP в своих проектах уже многие годы, никуда не собираясь мигрировать, а разрабы Slack вообще пишут в своём блоге о том, что PHP сейчас — это в целом лучший вариант для написания нового веб-проекта. Опять, наверное, наглый пиар разрабов PHP.
Я в своё время писал о Riot.js на хабре — меня точно также закидывали говном в комментариях. Это правда не помешало мне применить его в разработке двух больших международных проектов и одном своём личном. Да и сейчас он остаётся моим личным любимчиком для написания небольших решений в силу своей простоты.
Это я всё пишу к тому, что такой "экстермизм" в своих убеждениях, подобным вашим, когда один инструмент возвышается до небес, а другой втаптывается всеми правдами и неправдами на уровень "говёного поделия" — это больше от невежества и страстей, чем от нормального знания.
Вы же сейчас пытаетесь топить вообще против всякой функциональщины, притягивая за уши "доказательства" оттудова и отсюдова — откуда угодно, лишь бы доказать свою точку зрения. Неужели вы действительно уверены в том, что любой код, написанный в функциональном стиле — априори говёный? Вам самому не кажется это утверждение глупым? Неужели нельзя написать говнокод в императивном стиле? Вы что, не видели на своём веку такого? Так чего удивительного в том, что на функциональной логике тоже можно написать говнокод?
Вас бесит функциональщина за то, что она не решает все проблемы мира и даже не объясняет смысл жизни? Я с вами вообще не спорю на эту тему. Программирование — это всегда поиск компромиссов. Я не фанат функциональщины, не фанат редакса, не фанат реакта, не фанат ООП, императивщины и любого другого X. Я стараюсь изучать этот мир и применять то, что наиболее рационально в конкретной ситуации.
Я пишу статьи о Ramda для общего образования, чтобы знать, как она работает. Чтобы поэкспериментировать с нею в будущем. И если она докажет свою эффективность и полезность — то я скорее всего буду применять её в своих проектах. Даже если на хабре найдётся тысяча людей, которые из-за страстей своих назовут её говёным поделием, как назвали ранее массу других инструментов, признанных, при этом, всем остальным миром как доказавшие свою эффективность.
Моя к вам просьба: больше умеренности и мягкости. Это просто культура. Это уважение к людям, к моему труду в том числе. Я постараюсь просто отправлять ссылку на это сообщение, если в очередной раз буду видеть подобную тенденцию в общении. Я не желаю общаться на таком уровне, мне не интересно унижать одно и возвышать другое. Мне интересно принести людям пользу, дать им знания, и самому узнать что-то ещё, и умеренность и осторожность в своих выводах никогда не была плохой вещью в таких делах.
TheShock
1. Я предположил, что ФП вцелом сложнее поддерживается, а не сказал, что весь ФП — говнокод.
2. Да, я считаю, что Редакс поощряет говнокод, но, в первую очередь потому что у него такая архитектура, а не из-за ФП
3. Я считаю, что Редакс больше процедурный, чем функциональный. В нем есть функциональные элементы, но это не делает его полностью ФП
Аргументируйте
Всмысле «молиться»? Я никогда не молился, оно просто работает.
Подмена понятий. MobX не использует манки-патчинг.
А абстракции — зло? Вы из этих?
Или менее ясную структуру
То есть это про MobX выходят десятки статтей «как я сделал МобХ совсем другим»? Это в МобХ существуют mobx-thunk, mobx-saga, которые полностью меняют идеологию использования?
Скажите, вы пользовались MobX? Видимо, нет.
Потому что я Редаксом пользовался. И прекрасно знаю, что каждый пишет кто во что гаразд. Названия действий в константы выносятся или нет? Или может генерируются? Чистый или танки или саги? Или какая-то другая улучшалка? Композиция функций или миддливейр? Какой-то процент логики в Миддлвейре, какой-то во Вьшке, какой-то в АкшнКриэйторе. В итоге логика размазана фиг знает где.
Хреновая архитектура Редакса создает проблемы, которые решаются костылями. В МобХ этих проблем просто нет. Ты просто пишешь хороший, понятный код.
Опять же, очевидно, вы МобХ не пользовались.
Где вы успели сделать такой вывод?
Снова. Я что-то не увидел, как вы смогли прийти к этому.
Он не хвалит архитектуру или подход МобХ. Он признает, что по цифрам он быстрее его поделки. И что?
Не, погодите. Давайте еще раз определимся:
1. Я искренне считаю, что редакс — найбольший провал фронтенда за последнее время, это да. И понимаю, что он в этом похож на первые версии php — поощряющие быдлокодинг и потому столь популярные.
2. Я не вижу преимущест ФП, но правда хочу их увидеть. Пока я вижу, что на ФП удобно писать локальную обработку данных, а архитектуру вцелом писать императивно. Тут я подозреваю, что могу ошибиться, потому и холиварю, чтобы в споре прийти к истине.
3. Не смешивайте редакс и фп. Редакс функциональный больше в рекламных материалах, чем в реальной жизни.
Но ведь первые версии и правда были не очень. Вспомните «Волшебные кавычки», впомните Register_Globals, вспомните поглощение ошибок, впомните все документации, где примеры запросов делались с SQL-инъекциями. Я хорошо помню, что было безумно сложно найти тексты, где описаны хорошие, годные практики, а не всякое говнокодище.
И, возможно, такие комментарии как мои про редакс и привели к тому, что всего этого больше нет и phpшники поняли, что лучше пользоваться Laravel, а не быдлокодить, как это делали 80% пхп-программистов того времени. Или вы считаете, что они тоже обладали тайным знанием и все делали правильно, как сейчас пользователи редакс?
Обратите внимание, я пользовался в практической разработке обоими инструментами. И я к редаксу пришел открытый и без предрассудков. Вот правда. Я честно думал, что это — хороший инструмент. Пока не начал его использовать, пока не начал читать статьи о нем. Видите ли, я не успел поверить в то, что Редакс — идеален до того, как попользовался, меня не успела накрыть волна маркетинга и в итоге я просто сложил про него независимое мнение в поле.
Неправда.
Ни в коем случае. Потому я и прошу — покажите мне хороший, поддерживаемый код. Я хочу тоже научиться такой писать.
Потому что когда я прошу об этом, то мне, обычно показывают код совсем не поддерживаемый, а представляют как вершину мысли.
Я искренне верю, что его можно писать. Может, просто, JS не подходящий язык для этого?
Но я уверен, что код на Редаксе — априори говёный. Обратите внимание, не функциональный, а код на Редаксе. Меня, правда, некоторые люди убеждали, что пишут не говенный код, но так и не показали его. Пока весь код на редаксе, который я видел — или говенный или тудушка на 2 страницы.
Ни в коем случае. Я ею сам пользуюсь. Кое-где. Я даже хотел написать движок своей игры в функциональном стиле, ибо он идеально подходит под мою игру (значительно больше, чем ООП, правда), но не решился переходить на F#, а на C# иммутабельные объекты изменять слишком неудобно.
Zenitchik
Можно поинтересоваться, в целях повышения образованности, а чего конкретно не хватает JS чтобы быть подходящим для ФП?
TheShock
Я не утверждаю, что это так и есть, там стоял знак вопроса и я правда спрашиваю. Просто предположил на основе общения с людьми, которые пользовались другими ФП языками. Они утверждают, что на других языках и правда в ФП стиле код получается намного лучше, но мопед не мой.
Вот например, насколько я знаю, в них есть мощная система типов с автоматическим выведением, которая работает в Compile-time.
saggid Автор
Что аргументировать после того что было уже написано? Я уже объяснил всё наверху, как вы читали? Давайте я процитирую сам себя:
На тему магии.
Давайте я более подробно разверну вышенаписанную цитату того человека, который пишет на mobx и в том числе имеет свой небольшой бойлерплейт для него. Вы скорее всего статьи mobx не пишете и бойлерплейт для него не имеете, по крайней мере его слова будут более объективны в какой-то степени.
Он пишет в своей статье в одном из разделов под названием
3 Reasons Not to Use MobX
:Слишком много свободы для написания говнокода. Трудно отлаживать, если вылезут проблемы из-за магии и отсутствия нормальной истории изменений хранилища. И тут может быть более подходящая альтернатива.
Это как в Руби тоже всё "просто работает"? А потом оттуда уходят разрабы со словами о сообществе:
I’m pretty damn sure that my proposals would end up being drastically downvoted. Monkey-patches? C’mon, not a problem, we love our 10.years.ago! New abstractions? Who needs that, Rails is SIMPLE!
. Суть в том, что сейчас оно, возможно, работает, но в один прекрасный момент начнутся проблемы из-за магии, из-за которых вы проведёте 5 часов в дебаге и, надеюсь, сможете найти корень проблемы. А может и не сможете, прям как те авторы "говяной функциональщины", о которой вы ранее писали.Я где-то писал что он его использует? Помимо монки патчинга в мире есть другие заклинания, и он использует их. Мне это не нравится, я не склонен доверять магии просто изначально, и для меня такая методика работы — это и есть говнокод. Видимо, мы сейчас приходим к тому, что для каждого из нас понятие "говнокода" — это уже субьективная вещь, вам нравится магический mobx, мне нравится более явно работающий redux.
Господи, да просто потому что mobx в сотни раз менее популярен чем redux, всё. Был бы популярнее — имел бы точно такое же кол-во статей про варианты организации кода. Конечно в мелочах можно писать по-разному, но вы же понимаете, что основа остаётся той же самой: редюсеры, хранилище и экшены. Основа не меняется и легко улавливается.
Я-то не пользовался, но авторы вышепроцитированных статей его использовали, да и мой опыт говорит о моей нелюбви к магии.
Опять спорите. Это указание на его в целом желание добра и mobx в том числе. Если бы он занимался "наглым пиаром" своего говяного поделия, он наверное написал бы что-то мерзкое про mobx, вместо чего-то хорошего? Он таким не занимается, потому что, скорее всего, целей таких и не имел, а вам просто кажется.
Ну блин, и первые версии mobx тоже были не очень, я вполне уверен. Сейчас уже 4-я мажорная ветка развивается. Однако дело в том, что на PHP уже начиная с ерсии 5.3, которая вышла в 2009, можно было писать нормальные качественные проекты, чем люди и занимались. Но множество других продолжают называть PHP "говёным поделием" даже сегодня, в 2018-м году.
Ну я за вас рад, а я считаю это прорывом после нескольких лет эры бекбона. Я лично намного более счастлив с одним глобальным хранилищем, с ясной логикой проекта, когда я могу за пару недель войти в реально большой проект и понять всю его логику, просто потому что она везде однотипная. Вы считаете это говнокодом, я считаю это прекрасной в целом вещью. Опять видимо тут субьективщина пошла. Я не могу с этим ничего сделать, могу лишь сказать, что я с вами не согласен. Всё.
Да я вот тоже его использую и читаю статьи о нём в последний год примерно. Всё очень хорошо у меня лично. Вот честно не знаю, от чего у людей к нему прямо такое отторжение идёт. Удобный, понятный инструмент. Идея редюсеров мне очень нравится в целом.
Снова начинается желание доказать, что одно говно, другое не говно. Да и это чистая субьективщина. Я уже достаточно написал про редкас, мне опять самого себя цитировать и здесь? Вот в этом проблема. Я почти уверен, что если я приведу вам любой код, который не будет в вашем стиле — вы назовёте его "априори говёным". Ну и какой смысл тут общаться? Почему я вообще должен вам что-то доказывать в такой манере общения, когда вы называете всё, чего я придерживаюсь — "говёным поделием"? Вы понимаете, что культуры напиания кода могут быть разными? И каждая культура эффективна в той или иной мере. Мы с вами никогда не договоримся, если не будет этого понимания.
Возвращаясь к функциональщине. Я сам вообще далеко не спец в ФП. Я эти статьи перевожу для себя в том числе, чтобы лучше их понять, как я уже писал ранее. У меня даже проектов на ФП сейчас нет. Поэтому пока что и показывать в том числе нечего. И вообще ФП сейчас слишком неразвито, особенно в СНГ. Поэтому и примеров нормальных не имеется практически. Поэтому я и перевожу статью об основах работы с рамдой.
Каждый раз хочется закончить обсуждение, но каждый раз вы заставляете лезть объяснять вам всё, иногда цитируя себя же самого, что я писал ранее. Извините, наверное это всё-таки последний мой ответ вам. Время ценная штука, я уже потратил целый час на написание этого текста. Мог бы заняться чем-то более полезным. Цените время людей и цените своё время в том числе. До свидания.
saggid Автор
Да, вот кстати, на эту тему забыл ещё кинуть пару примеров, правда на англицком.
Обрабатываем логи вместе с Ramda и трансдюсерами
Используем Ramda вместе с Redux
Возможно я переведу эти статьи тоже после завершения перевода данного цикла статей о рамде, мне кажется, что они занимательные.
faiwer
Вы очень-очень ошибаетесь ;) Если не придерживаться вот этой мантры, то вы можете вкусить все 50 оттенков
redux+react
-а. А если вспомнить то, как готовят redux некоторые товарищи (например пишут почти всю логику в "экшонах"), то смею вас заверить, от некоторых проектов у вас… вам поплохеет.saggid Автор
Вообще, говоря честно, наверное надо было серию статей писать где-то на другом ресурсе. Теперь я это понимаю. Наверное я их все удалю и перенесу в какое-то другое место позже. Главное чтобы в поисковых системах светились. Честно говоря я просто устал, что вместо хоть сколько нибудь нормальной реакции получаешь как обычно копну зачастую нерациональной критики, на которую потом ещё тратится моё личное время и нервы. И почти ни одного поста, который дал бы мне нечто полезное. Зачем я вообще закидывал сюда посты о рамде? Чтобы какие-то старпёры хабра, простите, доказывали мне, что я идиот, который вообще не понимает зачем это нужно? Я не ради этого переводил статьи.
AngReload
Стыдно мне. Когда писал первый комментарий, не думал что этого дойдёт.
TheShock
Стыдно должно быть автору. Такие комментарии — это вполне нормально и устраивать из-за них истерику — это совершенно странно. Тем более на такую узкую тему.
Вы высказали свое мнение, высказали его корректно, а значит стыдаться тут нечего
saggid Автор
Вообще, там надо банально хранить где-то историю выбранных значений, наверное, чего в коде с использованием rxjs и рамдой не делается в данной реализации. Но это можно исправить, переписав немного код по другому. Примерно как в том примере с императивной реализацией, которую вы написали. Для человека достаточно развитого это вряд ли станет проблемой. Вся суть лишь в том, что надо индекс последнего активного элемента где-то хранить и вытаскивать при клике с зажатым шифтом. Вроде для меня всё стало ясно и понятно. Вопрос с "непонятностью" функциональщины на этом, надеюсь, в какой-то степени снимается с повестки)
Riim
> Кроме скорости разработки конкретных фич в проектах
сильно зависит от размера проекта. Небольшие проекты действительно очень интересно делать на ФП. Небольшие до того момента пока весь проект способен уместиться в голове одного человека. Когда проект выходит за эти рамки многие восхваляемые плюсы ФП показывают свою обратную сторону. Скорость добавления новых фич остаётся примерно на том же уровне, но вот добавляется их очень мало, тк. основное время уходит на латание багов, скорость добавления которых тоже бьёт все рекорды.
zvulon
очевидно это просто написать
тк понятно что происходит. А все эти lt, gt пусть в баше остаются
вобщем я тоже когда то кипятком писал о функциональщины и усложнения
потом все же образумился и теперь стараюсь писать как можно более тупой код.
императивный в основном. тк быстрее и проще.
saggid Автор
Ну окей, тогда код на рамде можно упростить ещё сильнее, на основе вашего подхода:
Я абсолютно серьёзно, оно работает, именно так, как надо.
Вы уже второй, кто меня пытается уличить в "усложнении", хотя я вообще в конкретно этой сериии статей вижу наоборот такие подходы, которые код как раз упрощают и сокращают, а не наоборот.
DarthVictor
Замена инфиксных и тернарных операторов на префиксные функции делает код декларативным?