Несмотря на то, что часть сообщества JS FP испытывает такое благоговение и поклонение неизменности, мое начало работы с этой концепцией вовсе не было связано с функциональным программированием.
Все началось с ESLint и правила no-param-reassign, которое защищает от повторного объявления параметра. Это были мои первые шаги с ESLint, и я шел с очень строгим подходом, чтобы наблюдать в процессе, какие из них мне действительно нужны. Если бы я их отфильтровывал или находил раздражающими, им пришлось бы уйти. (Более чем через шесть месяцев я отказался от некоторых из них; некоторые из них из-за того, что со-программисты не использовали ESLint.) No-param-reassign красиво выделено, и вскоре после того, как я начал его использовать, я встретил только один ситуация, которая убедила меня, что это правильный путь.
Искра
Итак, представьте это,
Для чего бы ни предназначалась эта функция, здесь это не имеет значения. Дело в том, что array теряется после первого цикла. Конечно, здесь можно было бы сделать ряд улучшений (например, сначала проверить условие или создать отдельный массив для новых значений), но в основном что-то подобное, только более императивное, произошло со мной. Мне нужно было снова использовать исходный массив, но он исчез навсегда. И тогда я знал.
Название
Погрузившись — а это единственный способ сделать что-либо — в мир функционального программирования, я обнаружил, что у того, чему я научился сам, есть имя. Это неизменяемость. Это означает, что вы не мутируете переменные. Вы не меняете их значение, вы не переназначаете их. Они священны.
(Не являясь носителем английского языка, я до сих пор связываю слово мутация с «Черепашками-ниндзя», так что вся эта тема имеет для меня около +5 к крутости. Но это только между прочим.)
Путь
Функциональные языки, такие как Haskell, имеют его по замыслу. В Haskell переменные даже не называются переменными — это объявления. Если ты хотел их изменить, БАХ!, ни за что, чувак. Эта агрессия не выдержит. JavaScript, будучи гораздо более гибким, не имеет таких механизмов защиты.
Но кто сказал, что так писать нельзя? ESLint уже помогает с параметрами, а аутсорсинг максимально чистых функций, которые должны быть максимально лаконичными, упрощает управление переменными. Как только вы установите его, оставьте его. Это так просто.
Отсюда и название этой истории, поскольку неизменность в JS возможна, но требует дисциплины, осторожности и характера.
Давайте теперь запустим несколько примеров.
По умолчанию примитив
Вопрос значения по умолчанию:
Функция safeGuardES5 — это обычный способ запуска с ней. Обходной путь ради обходного пути — «Для спорта», как мы сказали бы на моем родном языке — будет safeGuardES5immutable, который оставляет параметр, переданный в функцию, нетронутым. Имейте в виду, на данный момент мы на самом деле делаем это ради спорта, изучаем некоторые идеи, чтобы увидеть, куда они нас приведут. Практическое использование зависит от читателей.
Существует функция safeGuardES6, которая использует собственное значение по умолчанию, которое не создает дополнительную переменную, но в случае, если нам нужна исходная (будет undefined, но все же), у нас ее не будет. Хотя, я не думаю, что это случилось со мной, отсюда и широко распространенная практика перезаписи неопределенного параметра. Приемлемо, но мутирует.
Для более чем двух параметров я использую объект, и это позволяет мне сделать это:
Сложные массивы
Примитивы покрылись. Но что с массивами? Начнем с примера из реальной жизни:
Раздел before показывает код, который я нашел, и после код, который я оставил. Здесь довольно интересная ситуация. Давайте проследим за getNewOrder потоком:
Если нет startAt, вернуть массив. В противном случае,
- Вырежьте часть массива от
startAtдо конца массива. spliceвозвращает вырезанную часть.- Объедините эту часть с остальной частью массива.
Работает, но только в первый раз, во второй раз this.array — это только первая часть исходного массива. Сложность массивов в JavaScript заключается в том, что они являются экземплярами объекта Array. Это частные случаи Object. И как таковые они передаются в качестве ссылки на реальный объект, висящий где-то в памяти. Как только он изменился внутри функции, он изменился везде. Поэтому в переработанной версии я использую slice и concat, которые оставляют исходный объект нетронутым.
(Splice может быть очень опасным, потому что он мутирует объект и возвращает часть, которая была взята, оставляя оригинал необратимо искалеченным. Но это история для другой истории.)
И здесь неизменяемость становится чем-то большим, чем просто стилистический вопрос, как это было в основном со значением по умолчанию. С нашей оригинальной функцией мы мутировали объект (массив), который также использовался в других методах.
Объекты
ES6 принес нам const, который предотвращает переназначение переменной (теперь постоянной), а примитивы по своей конструкции неизменяемы, но что с объектами? Пример выше с Array показал опасность этого.
В массиве гораздо больше методов, которые позволили бы нам контролировать ситуацию. С помощью array.slice(0) мы можем создать поверхностную копию array. И возразить? Есть Object.assign(), но опять же, как и все объекты в JS, это сложно:
Другими словами, следите за своими пальцами.
Мы можем заморозить объект, но опять же, это влияет только на первый уровень. Как это:
Как правило, работа с объектами более сложна, и я стараюсь избегать действий, кроме их создания и чтения из них, и это может быть лучшим подходом.
Вывод
Моя цель сегодня состояла не в том, чтобы представить некоторые готовые к использованию рецепты и решения, а в том, чтобы намекнуть на философию видения вещей в коде с учетом определенных аспектов. Есть инструменты, помогающие нам отлавливать проблемные ситуации (ESLint), есть библиотеки (например, Facebook Immutable.js), есть целые языки, которые компилируются в JavaScript (я смотрю на тебя, Elm), и пользоваться ими нормально. Все, что оптимизирует вашу работу, стоит того, чтобы это использовать. Но я хочу настроить свой разум, чтобы сам видеть эти ситуации, инстинктивно. Сделанное правильно в первую очередь избавит меня от проблем позже.