Как начать думать функционально в JavaScript
Функциональное программирование - это стиль программирования, который требует от практикующего мышления более абстрактного уровня. Как правило, когда мы учимся программировать, мы очень тщательно и процедурно продумываем проблемы. Сначала мы делаем это, затем мы делаем то и т.д.
Мы настолько обеспокоены синтаксисом и языковыми правилами, что идея повышения уровня абстракции не входит в мыслительный процесс.
Это очень плохо, потому что позже, когда мы перестанем понимать языковые правила и синтаксис, нам придется переучивать свой мозг, чтобы мыслить по-другому.
Функциональное программирование или FP, как называют крутые ребята, могут потребовать некоторого времени, чтобы привыкнуть, но как только вы ухватитесь за концепции, это откроет целый новый мир. Это также позволяет вам разрабатывать код, о котором гораздо легче рассуждать.
FP позволяет вам перейти от императивного стиля кодирования к более декларативному стилю.
Популярный способ описать разницу между двумя заключается в том, что декларативное программирование фокусируется на том, чего должна достичь программа, в то время как императивное программирование фокусируется на том, как программа должна достичь результата.
Если это все немного туманно, это нормально. Просто сидите спокойно, и мы проработаем это вместе.
Давайте посмотрим на простой пример в JavaScript.
Императивный путь
Допустим, у нас есть множество домашних животных.
let pets = [
{ name: 'fluffy', color: 'white', age: 6, type: 'dog' },
{ name: 'molly', color: 'brown', age: 9, type: 'cat' },
{ name: 'buster', color: 'black', age: 3, type: 'dog' },
{ name: 'grant', color: 'black', age: 6, type: 'cat' },
{ name: 'pepsi', color: 'grey', age: 4, type: 'dog' },
{ name: 'winston', color: 'brown', age: 8, type: 'dog' },
{ name: 'sprite', color: 'white', age: 10, type: 'cat' },
{ name: 'oscar', color: 'grey', age: 2, type: 'dog' },
{ name: 'bumper', color: 'black', age: 12, type: 'dog' },
{ name: 'happy', color: 'white', age: 11, type: 'dog' },
{ name: 'speedy', color: 'black', age: 9, type: 'cat' }
];
Каждый элемент массива является объектом, определяющим домашнее животное.
Давайте предположим, что бизнес-требования гласят, что мы должны фильтровать объекты по этим критериям: домашнее животное старше 7 лет и имеет черный мех.
Для каждой подходящей записи нам нужно создать строку в следующем формате:
petName имеет возраст age и color цвет.
Теперь, когда мы знаем, каким должен быть результат, как мы его получим?
Во-первых, давайте посмотрим на императивный подход к проблеме. Вот код
let outputArr = [];
for (let index = 0; index < pets.length; index++) {
const pet = pets[index];
if (pet.age > 7 && pet.color == 'black') {
outputArr.push(`${pet.name} is ${pet.age} years old and is ${pet.color} in color.`);
}
}
console.table(outputArr);
В строке 1 мы создаем новый массив для сбора результатов, соответствующих бизнес-требованиям. Затем в строке 2 мы создаем цикл for. В пределах каждой итерации цикла мы помещаем питомца в строку 3 и проверяем его свойства в строке 4, чтобы увидеть, соответствует ли он нашим критериям.
Если тест пройден успешно, мы помещаем новую строку в массив outputArr.
Наконец, мы вызываем console.table() в строке 8, передавая наш вновь созданный массив для печати на консоль.
Вот вывод.
Оглядываясь на исходные данные, мы видим, что наш код работает правильно. Есть только 2 записи, которые соответствуют нашим критериям фильтра.
В этом коде нет ничего плохого. Это работает, но, безусловно, требует, чтобы мы погрузились в код и подумали о каждой детали. Мы четко заявляем каждый шаг о том, как получить то, что мы хотим.
Есть ли способ лучше? Я думаю да. Давайте посмотрим на это.
Декларативный путь
Если мы остановимся на минуту и подумаем о том, что мы хотим сделать с более высокого уровня, что мы придумаем. На простом понятном языке я бы сказал:
«Я хочу отфильтровать массив на основе набора критериев и выдать строку в качестве результата».
Я намеренно не думаю о реализации. Мои мысли на 10000 футов выше проблемы, а не на 10 футов.
Слова, которые выскакивают у меня здесь, являются фильтром и производят строку. Имея это в виду, как насчет чего-то вроде этого:
pets.filter().map()
и фактическая реализация
console.table(
pets.filter(pet => pet.age > 7 && pet.color == 'black')
.map(pet => `${pet.name} is ${pet.age} years old and is ${pet.color} in color.`)
);
Намного легче для чтения, не так ли? Это почти читается как английский. Это то, что люди имеют в виду, когда говорят, что FP декларативный. Мы заявляем, что мы хотим. Конечно, мы все еще должны предоставить логику фильтра, но это гораздо более элегантное решение, и его легче воспринимать. Мне не нужно проверять каждую строку цикла for, чтобы понять, что происходит.
Я бы пошел еще дальше. Чтобы остаться верным правилу DOT (Do One Thing), я бы реорганизовал этот код в это.
let ageFilter = (pet) => (pet.age > 7);
let colorFilter = (pet) => (pet.color == 'black');
let stringMaker = (pet) => `${pet.name} is ${pet.age} years old and is ${pet.color} in color.`;
console.table(pets.filter(ageFilter).filter(colorFilter).map(stringMaker));
Я разбил логику фильтра на две отдельные функции (помните: Do One Thing), а также логику для создания строки.
Затем, как вы можете видеть в строке 5, на самом деле это просто вопрос объединения функций .filter()
и .map()
вместе, передавая наши новые функции в качестве аргументов для получения нужного нам результата. Вывод в консоли точно такой же.
Ключевые моменты
- Функциональное программирование - это стиль программирования, который требует обдумывания ваших проблем на более абстрактном уровне.
- Большинство из нас учатся программировать на процедурном, императивном уровне, поэтому переход к ним может быть затруднен, когда вы только начинаете.
- Код, написанный в функциональном стиле, обычно легче воспринимать и легче читать.
- Функциональный код может быть легче поддерживать и изменять.