Однажды я прошел курс функционального программирования.

Как я ни старался в то время, я не мог уложить это в голове. Сегодня мне было бы трудно написать что-нибудь связное на Haskell, я все еще не мог рассказать вам, что такое монада, или объяснить все тонкости других математических концепций, связанных с функциональным программированием.

Однако со временем я стал по-настоящему ценить подход, поощряемый парадигмами функционального программирования. Концепции часто приводят к коду, который намного легче анализировать, тестировать, составлять и адаптировать с течением времени.

У меня есть проект, над которым я работал годами. Один из тех проектов «когда-нибудь закончу». Я часто использую его, чтобы попробовать разные подходы и библиотеки, поскольку он достаточно сложен, чтобы сделать его интересным примером, и я не слишком беспокоюсь о том, когда закончу его. Иногда, как говорится, речь идет больше о путешествии, чем о пункте назначения!

В этом посте я расскажу о некоторых уроках, которые я усвоил, добавляя Ramda в этот проект, чтобы помочь с более функциональным подходом.

Ramda — это набор функций, который «облегчает создание функциональных конвейеров». С ним довольно легко начать, но через некоторое время я усвоил несколько уроков о том, как извлечь из этого максимальную пользу.

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



«Адекватное введение в функциональное программирование
Эта статья является частью серии, в которой мы исследуем функциональное и реактивное программирование в целом... С тегом…dev. к"



Этот список библиотек и других ресурсов также стоит посмотреть:



Урок 1 — Названия функций Ramda могут сбивать с толку

У Ramda очень много функций, так что если вам нужно что-то сделать, наверняка для этого есть функция.

Однако мне было трудно найти то, что я искал, потому что некоторые названия казались немного неочевидными. Например, я нашел R.evolve полезным во многих случаях для применения различных преобразований к каждому свойству в объекте, но я нашел его только случайно.

Обязательно изучите документы, вы найдете несколько интересных инструментов!

Урок 2 — Будь проще

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

Давайте возьмем пример — скажем, вы хотите взять первый элемент из массива и вернуть этот элемент, если он больше 0, иначе вернуть undefined. С Ramda это может выглядеть примерно так:

R.ifElse(
 R.pipe(R.nth(0), R.gt(R.__, 0)),
 R.identity,
 R.always(undefined)
);

С другой стороны, эквивалентная лямбда-функция может выглядеть примерно так:

([elem]) => elem > 0 ? elem : undefined;

Хотя все методы, которые предлагает Ramda, могут быть полезны, довольно легко в конечном итоге уставиться на код, который вы только что написали, пытаясь вспомнить, что же вы вообще пытались сделать.

Ramda помогает писать код функциональным образом, а не является единственным способом написания функционального кода.

Урок 3 — Нужно передать переменные на промежуточную стадию? Используйте лямбду!

Как только вы начнете писать более сложные конвейеры, вы в конечном итоге достигнете точки, где у вас есть этап, которому требуется доступ к параметру, переданному в начале конвейера.

Мы могли бы изменить предыдущие этапы, чтобы пройти через этот параметр. Однако теперь мы информировали их о неважных для них знаниях (новый параметр) и привязывали их именно к этому пайплайну.

Вместо этого мы могли бы обернуть каждый метод в адаптер:

const passThrough = (func) => ([arg1, arg2]) =>
  ([func(arg1), arg2]);
R.pipe(
  (arg1, arg2) => ([arg1, arg2]),
  passThrough(stage1),
  passThrough(stage2),
  stage3,
  stage4);

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

Второй вариант, который я нашел, заключался в том, чтобы просто обернуть конвейер в лямбду:

(arg1, arg2) => R.pipe(
 stage1,
 stage2,
 stage3(arg2),
 stage4,
)(arg1);

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

Они предназначены для моей справки, но я надеюсь, что эти уроки будут полезны всем, кто начинает работать с Ramda. Если вы нашли что-то полезное при начале работы с Ramda или если у вас есть вопросы/мысли по поводу вышеизложенного, дайте мне знать!