что такое сайд эффект
Цикл for хорошо послужил нам, но он устарел и должен уступить место более новым, функциональным техникам программирования.
К счастью, этот факт не требует от вас быть мастером функционального программирования, более того, это то, что вы можете использовать в своих текущих проектах прямо сегодня!
Так в чем проблема цикла for в JavaScript?
Дизайн цикла for подталкивает к мутациям состояния (англ. mutation of state — изменение состояния — прим. переводчика) и применению сайд эффектов (англ. side effects — побочные эффекты — прим. переводчика), которые являются потенциальными источниками багов и непредсказуемого поведения кода.
Все мы слышали, что глобальное состояние — это плохо, и что мы должны избегать его. Однако, локальное состояние разделяет те же проблемы что и глобальное, мы просто не сталкиваемся с ними так часто, ведь они проявляются в меньших масштабах. Фактически, мы никогда не решали проблему, а просто сводили ее к минимуму.
Однажды, используя мутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика), значение случайной переменной изменится по неизвестной причине, и вы потратите часы на отладку и поиск причины изменения. У меня волосы на голове встают дыбом, только от одной мысли об этом.
Я бы хотел немного поговорить о сайд эффектах. Эти слова даже звучат ужасно, сайд эффекты. Дрянь. Вы хотите чтобы в ваших программах были сайд эффекты? Нет, я не хочу сайд эффектов в моих программах!
Но что такое сайд эффекты?
Считается, что у функции есть сайд эффекты если она модифицирует что-то за пределами своей области видимости. Это может быть изменение переменной, пользовательский ввод, запрос к api, запись данных на диск, лог в консоль и т.д.
Сайд эффекты — действительно мощный инструмент, но с большой силой приходит большая ответственность.
Сайд эффекты, по возможности, должны быть устранены или хотя бы инкапсулированы так, чтобы мы могли их контролировать. Функции с сайд эффектами тяжелее читать и тестировать, избегайте их всегда, когда это возможно. К счастью, в рамках данной статьи, мы не будем беспокоиться о побочных эффектах.
Я собираюсь отрефакторить этот код шаг за шагом, чтобы вы смогли пронаблюдать как легко превратить ваш собственный код в нечто более прекрасное.
Во-первых, я извлеку условное выражение в отдельную функцию:
Выносить условия — это в целом хорошая практика. Изменение фильтрации с “меньше чем 7 месяцев” на “является ли котенком” — большой шаг вперед. Теперь код передает наши намерения гораздо лучше. Почему мы берем котов до 7 месяцев? Не совсем понятно. Мы хотим найти котят, так пусть код говорит об этом!
Другая польза в том, что isKitten теперь можно переиспользовать, а все мы знаем, переиспользование кода всегда должно быть нашей целью.
Следующее изменение — извлечь преобразование (или отображение) кота его в имя. Это изменение будет понятнее позднее, а сейчас просто доверьтесь мне.
Код отдающий предпочтение const перед var и let выглядит чертовски привлекательно
Конечно, мы могли использовать const с самого начала, так как он не делает сам объект иммутабельным (об этом больше в другой раз), но это придуманный пример, не наседайте!
И последнее изменение, я бы предложил также извлечь фильтрацию и отображение в функцию (для полного переиспользования кода).
Домашнее задание
Изучить извлечение методов filter и map из их объектов. Задание со звездочкой: исследовать композицию функций.
Что насчет break
Многие из вас спрашивали, “Что насчет break ”, посмотрите часть вторую серии “Переосмысление JavaScript: break это GOTO циклов”.
Заключение
Для вас это мелочь, но меня очень радует когда кто-то подписывается на меня на медиуме или в твиттере (@joelnet), а если вы считаете мой рассказ бесполезным, то расскажите об этом в комментариях.
Sagи из жизни
Доброго времени суток.
У вас тоже есть знакомый react-разработчик, который рассказывает восхитительные истории о сайд-эффектах в redux? Нет?! Могу я стать этим человеком?
Автор взял на себя смелость не писать вводную часть о том, что же из себя представляет библиотека redux saga. Он надеется, что в случае недостаточности данных великодушный читатель воспользуется поиском хабра или официальным туториалом. Приводимые примеры во многом упрощены для передачи сути.
Итак, для чего же я вас всех собрал. Речь пойдёт о применении redux saga на просторах боевых клиентов. А конкретнее о случаях более сложных и интересных чем «принять action => послать API запрос => создать новый action».
Надеюсь простимулировать более глубокое изучение данной библиотеки согражданами, а также поделиться удовольствием от того, как мудрёные асинхронные вещи становятся понятней и выразительней.
WebSockets
Use case: получение обновлений списка доступных вакансий от сервера в режиме реального времени по модели push.
Речь идёт конечно же об использовании веб-сокетов. Для примера возьмём socket.io, но по факту API сокетов здесь не имеет значения.
В сагах есть такое понятие как канал. Это шина сообщений через которую источник событий может общаться с их потребителем. Основное назначение каналов — общение между сагами и преобразование потока асинхронных событий в удобную для работы структуру.
По-умолчанию, store является основным каналом событий для redux saga. События приходят в виде action. Для работы с событиями не из store используются каналы.
Получается канал как раз то, что нужно для работы с асинхронным потоком сообщений из сокета. Давайте же создадим канал как можно скорее!
Но сперва создадим сокет:
А теперь объявим скромный список событий:
Далее — фабричный метод по созданию канала. Код создаёт метод для подписки на интересующие нас события из сокета, метод для отписки и, непосредственно, сам канал событий:
Напишем достаточно простую сагу, ждущую обновлений из сокета и преобразующую их в соответствующий action:
Осталось только привязать её к корневой саге:
Google Places Autocomplete
Use case: показ подсказок при вводе пользователем географической локации для последующего поиска недвижимости поблизости.
По сути нам нужны координаты, а пользователю человекочитаемое название желаемого района.
Казалось бы, чем эта задача отличается от скучного «action => API => action»? В случае с автодополнением мы хотим делать как можно меньше бесполезных вызовов к внешним ресурсам, а также показывать пользователю только актуальные подсказки.
Для начала напишем метод API утилизирующий Google Places Autocomplete Service. Из интересного здесь — ограничение подсказок в рамках заданной страны:
Есть метод API, который будем дёргать, можно приступать к написанию саги. Настало время пояснить за бесполезные запросы.
Реализация в лоб, когда пользователь набирает текст, а мы на каждое изменение, читай — на каждый символ, отправляем запрос к API — ведёт в никуда. Пока пользователь печатает ему не нужны подсказки. А вот когда он останавливается — самое время ему услужить.
Также мы бы не хотели оказаться в ситуация, когда у нас пользователь что-то напечатал, остановился, запрос к API ушёл, пользователь что-то допечатал, ещё один запрос ушёл.
Таким образом у нас создалась гонка между двумя запросами. События могут развиваться двумя вариантами и оба не очень приятные.
Например, неактуальный запрос завершиться раньше актуального и на какой-то миг пользователь увидит нерелевантные подсказки. Неприятно, но не критично.
Или актуальный запрос завершиться раньше неактуального и после мигания пользователь останется всё с теми же нерелевантными подсказками. Вот это уже критично.
Конечно же мы не первые кто сталкивается с такой проблемой и поможет нам техника известная как debounce — исполнение задачи только после N единиц времени с момента получения последнего события. Вот небольшой материал по этому поводу.
В redux saga эта техника реализуется с помощью двух эффектов — delay и takeLatest. Первый откладывает выполнение саги на указанное количество миллисекунд. Второй обрывает выполнение уже работающей саги при поступлении нового события.
Зная всё это напишем сагу:
Как и в прошлом примере осталось только привязать её к корневой саге:
Dropdowns Closer
Use case: закрытие самописных выпадающих списков при клике за территорией элемента управления.
По факту, это эмуляция поведения встроенного в браузер select. Причины, по которым может понадобиться написанный на div’ах выпадающий список, оставлю для фантазии читателя.
Ключевой особенностью решаемой задачи является прохождение события за пределами элемента управления, например, при клике вне списка.
Догадались? Да, здесь нам тоже помогут каналы. С помощью них будем превращать события клика, всплывающие на самый верх, в соответствующий action.
Хорошо бы иметь фабричный метод, создающий каналы для произвольного события window. А вот и он:
Создаём очень похожую на первый пример сагу (при желании можно создать фабричный метод для них):
Заинтересованные reducer’ы переведут элемент управление в закрытое состояние:
Сам выпадающий список должен делать остановку распространения события клика на каких-либо внутренних частях и самостоятельно посылать событие закрытия в store. Например, при клике для открытия:
Иначе список просто не откроется. Тоже касается клика при выборе опции.
El clasico, монтируем сагу:
Notifications
Use case: показ браузерных уведомлений о доступности новых вакансий, в случае, если вкладка находится в фоне.
В активной вкладке пользователь увидит изменение в специальном элементе управления и поэтому уведомления не уместны. А вот для фоновой вкладки может пригодиться. Разумеется, с разрешения пользователя!
Ещё хотелось бы при клике на уведомление переходить во вкладку и показывать новые вакансии. Если пользователь не реагирует, то закрываем уведомление. Для этого нам понадобиться ещё один полезный эффект — race. Он позволяет устроить гонку между несколькими другими эффектами. В большинстве случаев race используется для обеспечения таймаута какой-либо операции.
Опустим код, отслеживания активности вкладки, по причине идентичности с кодом перехвата клика из предыдущего примера.
Напишем фабричный метод, который будет создавать канал для запроса одобрения от пользователя на получение уведомлений:
В догонку ещё один фабричный метод, но уже с каналом для получения клика по уведомлению:
Оба канала одноразовые и максимум выстреливают одним событием, после чего закрываются.
Осталось ключевое — сага с логикой. Проверяем активна ли вкладка, запрашиваем разрешение, создаём уведомление, ждём клика или таймаута, показываем новые вакансии, делаем вкладку активной, после чего закрываем уведомление:
Монтируем сагу перед этим сделав feature-detection:
Global Event Bus
Use case: передача заданной категории событий между redux-сторами.
Необходима такая шина в случае присутствия на странице нескольких приложений с общими данными. При этом приложения могут внедряться независимо друг от друга.
Например, поисковая строка с фильтрами и результаты поиска в виде отдельных react-приложений. При изменении фильтров хочется, чтобы приложение результатов узнало об этом, если оно тоже находится на странице.
Воспользуемся стандартным эмиттером событий:
Уже любимый eventChannel превращает стандартный эмиттер в канал:
Сага получается достаточно простая — создаём канал и бесконечно принимаем события, либо внутренние, либо внешние. Если получили внутреннее событие, то отправляем в шину, если внешнее — в store:
И финальное — монтирование саги с нужными событиями:
Надеюсь удалось показать, что саги делают описание комплексных сайд-эффектов проще. Исследуйте API библиотеки, перекладывайте на свои кейсы, сочиняйте сложные паттерны ожидания событий и будьте счастливы. Увидимся на JS просторах!
Борьба с грязными побочными эффектами в чистом функциональном JavaScript-коде
Если вы пробуете свои силы в функциональном программировании, то это значит, что вы довольно скоро столкнётесь с концепцией чистых функций. Продолжая занятия, вы обнаружите, что программисты, предпочитающие функциональный стиль, похоже, прямо-таки одержимы этими функциями. Они говорят о том, что чистые функции позволяют рассуждать о коде. Они говорят, что чистые функции — это сущности, которые вряд ли будут работать настолько непредсказуемо, что приведут к термоядерной войне. Ещё вы можете узнать от таких программистов, что чистые функции обеспечивают ссылочную прозрачность. И так — до бесконечности.
Кстати, функциональные программисты правы. Чистые функции — это хорошо. Но есть одна проблема…
Проблема чистых функций
Чистая функция — это функция, не имеющая побочных эффектов (на самом деле, это — не полное определение чистой функции, но мы к такому определению ещё вернёмся). Однако если вы хоть что-то понимаете в программировании, то вы знаете, что самое важное здесь как раз таки и заключается в побочных эффектах. Зачем вычислять число Пи до сотого знака после запятой, если никто это число не сможет прочесть? Для того чтобы вывести что-то на экран или распечатать на принтере, или представить в каком-то другом виде, доступном для восприятия, нам нужно вызвать из программы подходящую команду. А какая польза от баз данных, если в них ничего нельзя записывать? Для обеспечения работы приложений нужно считывать данные из устройств ввода и запрашивать информацию из сетевых ресурсов. Всё это нельзя сделать без побочных эффектов. Но, несмотря на такое положение дел, функциональное программирование построено вокруг чистых функций. Как же программистам, которые пишут программы в функциональном стиле, удаётся решить этот парадокс?
Если ответить на этот вопрос в двух словах, то функциональные программисты делают то же, что и математики: они жульничают. Хотя, несмотря на это обвинение, надо сказать, что они, с технической точки зрения, просто следуют определённым правилам. Но они находят в этих правилах лазейки и расширяют их до невероятных размеров. Делают они это двумя основными способами:
Внедрение зависимостей
Внедрение зависимостей — это первый метод работы с побочными эффектами. Используя этот подход, мы берём всё, что загрязняет код, и выносим это в параметры функции. Затем мы можем рассматривать всё это как нечто, входящее в сферу ответственности какой-то другой функции. Поясню это на следующем примере:
Тут хотелось бы сделать примечание для тех, кто знаком с сигнатурами типов. Если бы мы строго придерживались правил, то нам надо было бы учесть здесь и побочные эффекты. Но мы займёмся этим позже.
У функции logSomething() есть две проблемы, не позволяющие признать её чистой: она создаёт объект Date и что-то выводит в консоль. То есть, наша функция не только выполняет операции ввода-вывода, она ещё и выдаёт, при её вызове в разное время, разные результаты.
Как сделать эту функцию чистой? С помощью техники внедрения зависимостей мы можем взять всё, что загрязняет функцию и сделать это параметрами функции. В результате, вместо того, чтобы принимать один параметр, наша функция будет принимать три параметра:
Теперь, для того, чтобы вызвать функцию, нам надо самостоятельно передавать ей всё, что её до этого загрязняло:
Тут вы можете подумать, что всё это — глупость, что мы лишь переместили проблему на один уровень вверх, а это не добавило чистоты нашему коду. И знаете, это — правильные мысли. Это — лазейка в чистом виде.
Это похоже на притворную безграмотность: «Я и не знал, что вызов метода log объекта cnsl приведёт к выполнению оператора ввода-вывода. Мне просто это кто-то передал, а я знать не знаю, откуда всё это взялось». Такое отношение к делу — это неправильно.
Кроме того, важно отметить, что мы можем передавать нашей функции, которая раньше не отличалась чистотой, другие функции. Взглянем на другой пример. Представим, что в некоей форме имеется имя пользователя и нам нужно получить значение соответствующего поля этой формы:
Если надо, мы можем выносить непредсказуемые механизмы на уровни, ещё более отдалённые от основной функции. В итоге мы можем вынести их, условно говоря, в «пограничные области» кода. Это приведёт к тому, что у нас будет тонкая оболочка из нечистого кода, которая окружает хорошо протестированное и предсказуемое ядро. Предсказуемость кода оказывается крайне ценным его свойством тогда, когда размеры проектов, создаваемых программистами, растут.
▍Недостатки механизма внедрения зависимостей
Используя внедрение зависимостей можно написать большое и сложное приложение. Я это знаю, так как сам написал такое приложение. При таком подходе упрощается тестирование, становятся чётко видны зависимости функций. Но внедрение зависимостей не лишено недостатков. Главный из них заключается в том, что при его применении могут получаться очень длинные сигнатуры функций:
На самом деле, это не так уж и плохо. Минусы таких конструкций проявляются в том случае, если некоторые из параметров нужно передавать неким функциям, которые очень глубоко вложены в другие функции. Выглядит это как необходимость передавать параметры через много уровней вызовов функций. Когда число таких уровней растёт, это начинает раздражать. Например, может возникнуть необходимость в передаче объекта, представляющего дату, через 5 промежуточных функций, при том, что ни одна из промежуточных функций этим объектом не пользуется. Хотя, конечно, нельзя сказать, что подобная ситуация — это нечто вроде вселенской катастрофы. К тому же, это даёт возможность чётко видеть зависимости функций. Правда, как бы там ни было, это всё равно не так уж и приятно. Поэтому рассмотрим следующий механизм.
▍Ленивые функции
Взглянем на вторую лазейку, которую используют приверженцы функционального программирования. Она заключается в следующей идее: побочный эффект — это не побочный эффект до тех пор, пока он не произойдёт на самом деле. Знаю, звучит это таинственно. Для того чтобы в этом разобраться, рассмотрим следующий пример:
Итак, перед нами пример нечистой функции. Она выводит данные в консоль и ещё является причиной ядерной войны. Однако вообразите, что нам нужен тот ноль, который эта функция возвращает. Представьте себе сценарий, в соответствии с которым нам надо что-то посчитать после запуска ракеты. Скажем, нам может понадобиться запустить таймер обратного отсчёта или что-то в этом роде. В данном случае совершенно естественным будет заранее подумать о выполнении вычислений. И мы должны позаботиться о том, чтобы ракета запустилась именно тогда, когда нужно. Нам не надо выполнять вычисления таким образом, чтобы они могли бы случайно привести к запуску этой ракеты. Поэтому подумаем над тем, что произойдёт, если мы обернём функцию fZero() в другую функцию, которая просто её возвращает. Скажем, это будет нечто вроде обёртки для обеспечения безопасности:
Есть ли у неё побочные эффекты? Мы только что выяснили, что вызов returnZeroFunc() не приводит к запуску ракет. Если не вызывать то, что возвращает эта функция, ничего не произойдёт. Поэтому мы можем заключить, что у этой функции нет побочных эффектов.
Является ли эта функция ссылочно прозрачной? То есть, всегда ли она возвращает одно и то же при передаче ей одних и тех же входных данных? Проверим это, воспользовавшись тем, что в вышеприведённом фрагменте кода мы вызывали эту функцию несколько раз:
Выглядит всё это хорошо, но функция returnZeroFunc() пока не вполне чиста. Она ссылается на переменную, находящуюся за пределами её собственной области видимости. Для того чтобы эту проблему решить, перепишем функцию:
Теперь функцию можно признать чистой. Однако в данной ситуации правила JavaScript играют против нас. А именно, мы больше не можем использовать оператор === для проверки ссылочной прозрачности функции. Происходит это из-за того, что returnZeroFunc() всегда будет возвращать новую ссылку на функцию. Правда, ссылочную прозрачность можно проверить, изучив код самостоятельно. Такой анализ покажет, что при каждом вызове функции она возвращает ссылку на одну и ту же функцию.
Перед нами — маленькая аккуратная лазейка. Но можно ли использовать её в реальных проектах? Ответ на этот вопрос положителен. Однако прежде чем поговорить о том, как пользоваться этим на практике, немного разовьём нашу идею. А именно, вернёмся к опасной функции fZero() :
Попытаемся воспользоваться тем нулём, который возвращает эта функция, но сделаем это так, чтобы (пока) ядерную войну не начинать. Для этого создадим функцию, которая берёт тот ноль, который возвращает функция fZero() и добавляет к нему единицу:
Вот незадача… Мы случайно начали ядерную войну. Попробуем снова, но в этот раз не будем возвращать число. Вместо этого вернём функцию, которая, когда-нибудь, возвратит число:
Теперь можно вздохнуть спокойно. Катастрофа предотвращена. Продолжим исследования. Благодаря этим двум функциям мы можем создать целую кучу «возможных чисел»:
Видите, что мы тут сделали? С «возможными числами» можно делать то же самое, что и с обычными числами. Математики называют это изоморфизмом. Обычное число всегда можно превратить в «возможное число», поместив его в функцию. Получить «возможное число» можно, вызвав функцию. Другими словами, у нас имеется маппинг между обычными числами и «возможными числами». Это, на самом деле, гораздо интереснее, чем может показаться. Скоро мы вернёмся к данной идее.
Вышеописанный приём с использованием функции-обёртки — это допустимая стратегия. Мы можем скрываться за функциями столько, сколько нужно. И, так как мы пока не вызывали ни одной из этих функций, все они, теоретически, являются чистыми. И войну никто не начинает. В обычном коде (не связанном с ракетами) нам, на самом деле, в итоге нужны побочные эффекты. Оборачивание всего, что нужно, в функции, позволяет нам с точностью контролировать эти эффекты. Мы сами выбираем время появления этих эффектов.
Функтор Effect
Теперь опишем функцию-конструктор для создания объектов типа Effect :
Так… Сейчас у нас нет возможности наблюдать за тем, что тут произошло. Поэтому давайте модифицируем Effect для того, чтобы, так сказать, получить возможность «спускать курок»:
Если будет нужно, мы можем продолжать вызывать функцию map() :
Вот здесь происходящее уже начинает становиться интереснее. Мы называем это «функтором». Всё это означает, что у объекта Effect есть функция map() и он подчиняется некоторым правилам. Однако это не такие правила, которые что-либо запрещают. Эти правила посвящены тому, что можно делать. Они больше похожи на привилегии. Так как объект Effect — это функтор, он подчиняется этим правилам. В частности это — так называемое «правило композиции».
Другими словами, два выполненных подряд метода map() эквивалентны композиции двух функций. Это означает, что объект типа Effect может выполнять действия, подобные следующему (вспомните один из вышеприведённых примеров):
Теперь предлагаю прекратить эксперименты с числами и поговорить о том, что больше похоже на код, используемый в реальных проектах.
▍Метод of()
Для того чтобы продемонстрировать ситуацию, в которой может пригодиться подобный метод, представим, что мы работаем над веб-приложением. У этого приложения есть некие стандартные возможности, скажем, оно может выводить список статей и сведения о пользователе. Однако расположение этих элементов в HTML-коде у разных пользователей нашего приложения различается. Так как мы привыкли принимать продуманные решения, мы решили хранить сведения о расположении элементов в глобальном конфигурационном объекте. Благодаря этому мы всегда сможем к ним обратиться. Например:
▍Создание структур из вложенных объектов Effect и раскрытие вложенных структур
Теперь, если нам нужно всё это объединить, мы можем воспользоваться функцией map() :
Хотя и это — тоже не вполне корректно. Скорее это будет выглядеть так:
Если раскрыть это выражение, то получится следующее:
Обратите внимание на то, что весь код, который выполняет реальные действия, находится в самой глубоко вложенной функции. Во внешний объект Effect он не попадает.
▍Метод join()
Эту функцию мы можем использовать для того, чтобы извлечь из вложенной конструкции элемент со сведениями о пользователе:
▍Метод chain()
Мы назвали эту новую функцию chain() из-за того, что она позволяет объединять операции, выполняемые над объектами Effect (на самом деле, мы так назвали её ещё и потому что стандарт предписывает называть подобную функцию именно так). Теперь наш код для получения внутреннего HTML-кода блока со сведениями о пользователе будет выглядеть так:
▍Комбинация объектов Effect
Пока всё выглядит нормально. Теперь давайте получим нужные данные:
Происходящее станет понятнее, если взглянуть на типы. Сигнатура типа для метода map() выглядит примерно так:
Вот сигнатура функции для работы с шаблонами:
Вышеприведённый пример с использованием функции liftA2() можно переписать так:
Зачем это всё?
Сейчас вы можете подумать о том, что для того, чтобы избежать побочных эффектов, требуются немалые усилия. Что это меняет? У вас может возникнуть ощущение, что упаковка сущностей в объекты Effect и возня с методом ap() предусматривают выполнение слишком больших объёмов сложной работы. Зачем это всё, если обычный код и так вполне нормально работает? И понадобится ли вообще нечто подобное в реальном мире?
Позволю себе привести тут одну известную цитату Джона Хьюза из этой статьи: «Функциональный программист смотрится как средневековый монах, отвергающий удовольствия жизни в надежде, что это сделает его добродетельным».
Рассмотрим вышеозначенные замечания с двух точек зрения:
▍О важности функциональной чистоты кода
Чистые функции — это важно. Если рассмотреть маленькую функцию в изоляции, то некоторая доля конструкций в ней, не позволяющих назвать её чистой, особого значения не имеет. Написать нечто вроде const pattern = window.myAppConfig.templates[‘greeting’]; быстрее и проще, чем писать, например, так:
И если это — всё, что вы когда либо делали в программировании, то первый вариант, и правда, проще и лучше. Побочные эффекты в таком случае значения не имеют. Однако это — всего одна строка кода, находящаяся в приложении, которое может содержать тысячи или даже миллионы строк. Функциональная чистота начинает приобретать куда большее значение, когда вы пытаетесь выяснить, почему приложение таинственным образом, и, как кажется, совершенно без причин, перестаёт работать. В подобной ситуации происходит нечто неожиданное, и вы пытаетесь разбить проблему на части и изолировать её причину. Чем больше кода вы сможете исключить из рассмотрения — тем лучше. Если ваши функции являются чистыми, это позволяет вам быть уверенным в том, что на их поведение влияет только то, что им передают. И это значительно сужает диапазон возможных причин ошибки, которые вам нужно рассмотреть.
Другими словами это позволяет вам тратить меньше усилий на размышления. Это крайне важно при отладке больших и сложных приложений.
▍Паттерн Effect в реальном мире
Скажем, у вас есть некие данные. Причём этих данных у вас довольно много. Это могут быть миллионы строк в виде текстовых CSV-файлов или огромные таблицы базы данных. Вы должны эти данные обработать. Возможно, вы, используя их, обучаете нейронную сеть для построения модели логического вывода. Возможно, вы пытаетесь предсказать следующее крупное движение на рынке криптовалют. На самом деле, цель обработки больших объёмов данных может быть какой угодно. Дело тут в том, что для того, чтобы достичь этой цели, требуется выполнить огромный объём вычислений.
Для того чтобы сделать это возможным, вам нужно сначала описать вычисления, которые вы хотите выполнить. Но описывать их нужно, не выполняя реального запуска таких вычислений. Ничего не напоминает? В идеале, после того, как вы создадите подобное описание, вы передаёте его некоему фреймворку. Этот фреймворк затем позаботится о чтении информации из хранилища и о распределении задач по узлам обработки данных. Затем тот же фреймворк соберёт результаты и сообщит вам о том, что получилось после выполнения вычислений.
Именно так работает опенсорсная библиотека TensorFlow, предназначенная для выполнения высокопроизводительных численных расчётов.
Используя TensorFlow, не применяют обычные типы данных языка программирования, на котором пишут приложение. Вместо этого создают так называемые «тензоры». Скажем, если нам надо сложить два числа, то выглядеть это будет так:
Итоги
Оба рассмотренных подхода — это своего рода жульничество. И тот и другой не позволяют избавиться от конструкций, нарушающих функциональную чистоту кода. Они лишь выносят подобные конструкции, так сказать, на периферию. Но это весьма полезно. Это позволяет чётко разделять код на чистый и нечистый. Возможность подобного разделения может дать программисту реальные преимущества в ситуациях, когда ему приходится отлаживать большие и сложные приложения.
Уважаемые читатели! Пользуетесь ли вы концепциями функционального программирования в своих проектах?