в функциональном программировании нельзя

Функциональное программирование: дурацкая игрушка, которая убивает производительность труда. Часть 1

Возможно, вы уже слышали о так называемом «функциональном» программировании. Возможно, вы даже подумываете о том, что вам стоит его как-нибудь попробовать.

Ни в коем случае этого не делайте!

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Функциональное программирование полно недочётов, оно не подходит для реальных проектов. Его применение приведёт к резкому падению производительности труда. Почему это так? Давайте выясним.

Функциональное программирование не может удовлетворить многогранным корпоративным требованиям

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Для создания реального ПО корпоративного класса требуется сложный набор жёстких и обязательных к исполнению требований, относящихся к ожидаемому количеству абстракций, внедрённых в программное решение. Объектно-ориентированное программирование позволяет разработчику использовать множество механизмов абстрагирования, которые способны полностью удовлетворить требованиям, предъявляемым организациями к программным системам.

Полагаю, вышеприведённый текст особенно понятным не выглядит. Но совсем скоро всё встанет на свои места.

Так называемое «функциональное» программирование не имеет нормального механизма создания абстракций. Дело в том, что оно основано на математических правилах (эти правила, что совершенно понятно, не находят применения в реальном мире, находящемся снаружи стен учебных заведений). В отличие от ООП, функциональное программирование не делает никаких попыток пойти навстречу всем тем многочисленным, жёстким и сложным требованиям к ПО, которые предъявляют организации.

В следующем фрагменте кода продемонстрированы проблемы, широко распространённые там, где пользуются функциональным программированием:

Если это всё ничего кроме злости у вас не вызывает, то знайте, что вы в этом не одиноки!

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

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

Функциональные программные решения не выдерживают проверку временем

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

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

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

Как видите, вся основная функциональность здесь качественно абстрагирована. Этот — цельный код.

Не позволяйте простоте одурачить себя. Он полностью удовлетворяет бизнес-требованиям к коду, которые обычно выдвигают серьёзные большие организации.

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

Серьёзный менеджмент нуждается в серьёзных возможностях

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

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

Как известно любому настоящему проект-менеджеру, трудящемуся в корпоративной среде, реальную ценность для бизнеса представляют лишь новые возможности приложений.

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

Очевидно то, что в этой сфере у так называемого «функционального» программирования имеются большие проблемы. Оно неоправданно упрощает выполнение таких ненужных действий, как рефакторинг и модульное тестирование. Это, в свою очередь, служит отвлекающим фактором для команды разработчиков. Члены этой команды могут случайно потратить время на эти бесполезные занятия вместо того, чтобы оснащать приложение новыми возможностями.

Следующий пример ясно показывает неполноценность функционального программирования. Использование этой методологии слишком сильно упрощает рефакторинг:

Если вас воротит от простоты этого рефакторинга — знайте, что такие ощущения испытываете не только вы. До рефакторинга было шесть строк кода, после стало семь строк? Это что — шутка?

Сравним это с нормальным рефакторингом объектно-ориентированного кода:

Именно так выглядит настоящее программирование! До рефакторинга было девять строк кода, а после стало 22. Для выполнения этой работы требуется приложить больше усилий. Это заставит программистов, работающих на компанию, дважды подумать перед тем, как ввязываться в подобную авантюру.

Ущербная концепция декларативного подхода к программированию

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Так называемые «функциональные» программисты неоправданно гордятся собой из-за того, что пишут декларативный код. Но тут нечем гордиться — подобный код лишь создаёт иллюзию продуктивности.

Основная сфера ответственности любого разработчика должна заключаться в размышлениях о подходящих жёстких объектно-ориентированных абстракциях (этого, кроме того, требует любая серьёзная большая организация).

Взглянем на хорошо абстрагированный объектно-ориентированный код:

Декларативный код, с другой стороны, является слишком конкретным, он, что неправильно, заставляет разработчика ориентироваться на менее важные вещи вроде бизнес-логики. Сравните мощное решение корпоративного уровня, описанное выше, со следующим образцом неполноценного «декларативного» кода:

Моделирование реального мира

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Объектно-ориентированное программирование — это гениально. В отличие от «функционального» программирования оно отлично подходит для моделирования объектов реального мира. Это возможно благодаря тому, что ООП поддерживает такие продвинутые технологии, как наследование, полиморфизм и инкапсуляция.

Любой уважающий себя программист должен ежедневно использовать наследование для достижения высокого уровня многократного использования кода. Как уже было сказано, наследование отлично подходит для моделирования реального мира. Кошки, например, всегда наследуют свои свойства и поведение от единственного абстрактного животного из реального мира. Жизнь зародилась в океане несколько миллиардов лет назад. В результате все млекопитающие (включая кошек) унаследовали свойства от некоей первозданной рыбы. Например, говоря объектно-ориентированным языком, это может быть нечто вроде garfield.fishHead — свойства, описывающего рыбью голову кота Гарфилда. То же самое можно сказать и о поведении, что, в терминологии ООП может выглядеть как garfield.swim() (плавание) и garfield.layCaviar() (икрометание). Никого не удивляет то, что кошки так сильно любят принимать ванны и плавать! Люди, в сущности, это то же самое. Человек, если захочет, легко может начать метать икру!

Наши программы всегда должны следовать похожим иерархическим подходам к организации кода. Функциональное программирование неоправданно лишает программистов замечательных конструкций по совместному использованию кода, создателей которых вдохновила сама природа. У этого есть далеко идущие последствия — особенно если речь идёт о серьёзных сложных корпоративных приложениях.

Функции всегда должны быть привязаны к объектам

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Кто, в конце концов, будет координировать все составные части этого сложного процесса? А люди достаточно умные могут нанять и несколько менеджеров, поступив в точности так, как рекомендует ООП.

Несложно заметить, что это — очередной гвоздь в «функциональный» гроб, так как функциональное программирование не пытается моделировать реальный мир. Функциям позволено существовать отдельно от объектов, а это просто неправильно.

Функциональное программирование, очевидно, не подходит для любой серьёзной работы, ориентированной на реальный мир.

Функциональное программирование не даёт возможностей для профессионального роста

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

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

Во-первых, программисту нужно изучить продвинутые ООП-техники вроде наследования, абстракции, инкапсуляции и полиморфизма. Затем нужно хорошо освоить тьму паттернов проектирования (вроде паттерна «Синглтон») и начать использовать их в своём коде. Существует около 30 базовых паттернов проектирования, которые нужно очень хорошо знать. В идеале где-то в процессе изучения паттернов программист должен начать использовать в своём коде различные абстракции корпоративного уровня.

Следующий шаг — ознакомление с технологиями наподобие Domain-Driven Design, и изучение того, как разделять на части монолитные программные проекты. Кроме того, рекомендовано изучение подходящих инструментов для рефакторинга кода вроде Resharper, так как рефакторинг объектно-ориентированного кода — задача нетривиальная.

Для того чтобы достичь достойного уровня в сфере ООП нужно 20-30 лет. Но надо отметить, что даже большинство тех, у кого наберётся 30 лет опыта, не могут считаться настоящими мастерами ООП. Путь ООП-ученика тяжёл и наполнен неопределённостью. Объектно-ориентированного разработчика, в результате, ждёт учёба длиною в жизнь. Это ли не прекрасно?

А как насчёт несчастных функциональных программистов? К сожалению, им нужно изучить не так уж и много. Я сам учил нескольких джуниоров функциональному программированию на JavaScript. У них начало неплохо получаться примерно через полгода. Им просто понадобилось понять несколько базовых концепций, а затем надо было научиться быстро их применять. Где тут упоение от пожизненной учёбы? Я бы им не позавидовал.

Уважаемые читатели! Что вам больше всего не нравится в функциональном программировании?

Источник

Языки функционального программирования: полное руководство

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Программирование в функциональном стиле фокусируется на чистых математических функциях, неизменяемых данных, логическом потоке и строгой типизации данных. Функциональные языки программирования противоположны объектно-ориентированным языкам, которые фокусируются на изменяемых данных и изменяемых состояниях.

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

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

Давайте определим эту парадигму программирования (и парадигмы в целом), а затем рассмотрим некоторые плюсы и минусы функционального программирования.

Что такое парадигма программирования?

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

Войдите в парадигму программирования, которая представляет собой способ категоризации языков программирования по их центральной теории или методологии обработки данных. Языки подходят для парадигмы, имея ряд определяющих принципов. Существует множество парадигм программирования, многие из которых пересекаются или содержат другие парадигмы. Две основные парадигмы — функциональная и объектно-ориентированная, но существует гораздо больше способов обработки данных, не рассматриваемых этими двумя парадигмами.

Что такое функциональное программирование?

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

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

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

Функциональное программирование подпадает под зонтик парадигмы императивного программирования, противоположной декларативному программированию, в котором находится объектно-ориентированное программирование.

Функциональное программирование существует немного дольше, чем объектно-ориентированное программирование, ещё со времён машины Тьюринга. За последние пару поколений он пережил спад, но в последнее время довольно быстро вернулся в JavaScript, который не зависит от парадигм, но считается более функциональным языком, чем объектно-ориентированный.

Определение принципов функционального программирования

Функциональное программирование основывается на нескольких определяющих принципах, на которых также опираются все языки, поддерживающие эту методологию. Они есть:

Плюсы и минусы функционального программирования

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

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

Плюсы

Минусы

Функциональные языки программирования

Функциональное программирование намного старше объектно-ориентированного программирования, и в последние годы оно немного вернулось. Это возвращение в основном связано с языками из этого списка.

Хотя функциональное программирование занимает более особое место по сравнению с объектно-ориентированными языками. Его рост популярности означает потребность в более функциональных программистах в области, где их очень мало. Изучение функционального программирования приведёт вас к уникальному рынку вакансий, который сам по себе является полезным и приятным.

Источник

Шпаргалка по функциональному программированию

Привет, меня зовут Григорий Бизюкин, я преподаватель Школы разработки интерфейсов и фронтенд-разработчик в Яндексе. Давайте поговорим о функциональном программировании в мире JavaScript. Мы все про ФП что-то слышали, нам всем оно интересно, но у меня, когда я искал полезные материалы для подготовки к лекциям, сложилось такое впечатление: есть куча статей, каждая из которых либо говорит об ФП общими словами, либо раскрывает отдельный маленький кусочек темы, чего, конечно, недостаточно.

в функциональном программировании нельзя. Смотреть фото в функциональном программировании нельзя. Смотреть картинку в функциональном программировании нельзя. Картинка про в функциональном программировании нельзя. Фото в функциональном программировании нельзя

Добавим функционального света

Впервые я попробовал обобщить в одном месте самые популярные и, как мне кажется, применимые приёмы функционального программирования в лекции для ШРИ. Потом захотелось расширить материал и рассмотреть ещё больше концепций. В результате получилась эта статья. В ней мы разберём всё самое сложное простым языком с понятными примерами. Надеюсь, вам будет интересно!

Функциональное программирование

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

Важно понимать, что когда на глаза попадается очередная статья «Почему разработчик обязательно должен знать ФП», то автор, вероятно, говорит именно о нескольких подходах из мира ФП, которые можно применить у себя на проекте, чем о том, что вам пора пересесть за Haskell.

Функциональное программирование — штука интересная, но вряд ли вы захотите переписать весь проект на функциональном языке. На практике именно отдельные подходы оказываются самыми полезными и применимыми. На них и сконцентрируемся. В контексте ФП часто можно встретить термины вроде линз и монад. Здесь они останутся за скобками, потому что уж слишком специфичны.

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

За и против

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

В целом считается, что ФП делает код понятнее, потому что является более декларативным. Остальные рассуждения оставим за скобками, так как на Хабре уже достаточно статей, где рассмотрены разные аргументы как за ФП, так и против. При желании можно обратиться к ним, чтобы решить для себя, когда вы хотите использовать ФП, а когда нет. Здесь мы сосредоточимся на объяснении терминов и подходов.

Императивный vs декларативный

Императивный подход говорит о том, как решать задачу, декларативный — что хотим получить в результате.

В жизни мы, как правило, думаем о результате. Например, когда просим маму приготовить пиццу, делаем это декларативно: «Мам, хочу пиццу!» Если бы мы делали это императивно, то получилось бы что-то вроде: «Мама, открой холодильник, достань тесто, возьми помидоры» и так далее.

В разработке та же история. Когда мы пишем декларативно, код выглядит гораздо проще:

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

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

Нам неважно, как именно браузер будет парсить css, искать на странице элемент, соответствующий селектору, и перекрашивать его в определённый цвет. Мы говорим только о том, что хотим получить.

Такая же история и с SQL:

Запрос говорит о результате, а не о том, как именно его выполнить.

Функции и процедуры

Функция — понятие, близкое к математическому. Она что-то получает на вход и всегда что-то возвращает.

Процедура, в свою очередь, вызывается ради побочных эффектов:

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

В JS не существует процедур, потому что то, что мы считаем процедурой, на самом деле является функцией без return. Если опустить return, функция всё равно неявно возвращает undefined и остаётся функцией.

Но в функциональном программировании мы стремимся как можно больше использовать функции, которые явно что-то возвращают.

Параметры и аргументы

Параметры — это переменные, созданные в объявлении функции. Аргументы — конкретные значения, переданные при вызове.

Сигнатура

Количество, тип и порядок параметров. Объявление функции в JS не содержит информации о типе параметров из-за динамической типизации. Если не используется TypeScript, эту информацию можно указать через JSDoc.

Арность

Арность — количество параметров, которые принимает функция. В JavaScript арность функции можно определить при помощи свойства length.

У свойства length есть особенности, которые следует учитывать:

Рекурсия

Когда функция вызывает саму себя, происходит рекурсивный вызов. Для его корректной работы необходимо, чтобы внутри функции было хотя бы одно рекурсивное условие, на которое мы обязательно рано или поздно выйдем. Если этого не произойдёт, программа зациклится.

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

Несмотря на заманчивые возможности, поддержка хвостовой рекурсии до сих пор отсутствует и вряд ли появится в будущем, поэтому сведения о ней носят чисто теоретический характер. Если добавить в самое начало функции console.trace, можно убедиться, что каждый новый вызов создаёт новый кадр в стеке, несмотря на то, что условия рекурсии выполняются. Более подробно об оптимизации хвостовых вызовов можно почитать здесь.

Функция первого класса

Функции, которые мы можем использовать как обычные объекты, называются функциями первого класса. Их можно присваивать, передавать в другие функции и возвращать.

Функция высшего порядка

Функции, которые принимают или возвращают другие функции. С ними мы работаем каждый день.

При этом высшим порядком могут быть не только функции, но и, например, компоненты в React, принимающие или возвращающие другие компоненты. Они, соответственно, называются компонентами высшего порядка.

Предикат

Это функция, которая возвращает логическое значение. Самый распространённый пример — использование предиката внутри функций filter, some, every.

Замыкание

Замыкание — это функция плюс её область видимости. Замыкание создаётся заново каждый раз при вызове функции и позволяет получить значение к переменным, объявленным во внешней функции.

В примере внутри замыкания хранятся две переменные: tag и count. Каждый раз, когда мы создаём новую переменную внутри другой функции и возвращаем её наружу, функция находит переменную, объявленную во внешней функции, через замыкание. Если тема замыканий кажется чем-то загадочным, почитайте о них подробнее в блоге HTML Academy.

Мемоизация

Полезный приём — функция кеширует результаты своего вызова:

Можно заметить, как одни возможности становятся базой для других, более сложных. Благодаря функциям первого класса становятся возможны функции высших порядков, благодаря которым становятся возможны замыкания. А благодаря замыканиям становится возможной мемоизация.

Конвейер и композиция

Конвейер и композиция — это вызов следующей функции с результатами предыдущей. В зависимости от того, в какую сторону мы передаём данные: слева направо или справо налево, получается либо конвейер, либо композиция.

Забавно, что в ООП тоже есть композиция, но она имеет там совершенно другой смысл.

Конвейер

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

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

Если бы у нас была функция pipe, которая аналогичным образом организовывает поток данных, через неё можно было бы записать так:

Аргумент, переданный в конвейер, последовательно проходит слева направо:

Хм, а что если запустить конвейер в другую сторону?

Композиция

Если запустить конвейер в обратную сторону, получится композиция. Композицию функций можно создать без операторов, просто вызывая каждую следующую функцию с результатами предыдущей.

Если записать то же самое через вспомогательную функцию compose, получится:

Внешне всё осталось почти так же, но место вызова функции increment изменилось, потому что теперь цепочка вычислений стала работать справа налево:

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

Таким образом, конвейер и композиция — это два направления одного потока данных.

Преимущества

Когда поток данных организован через вспомогательные функции pipe или compose, больше не нужно писать много скобок, кроме того, код выглядит более декларативным, то есть более читаемым. Но есть ещё два момента, которые можно легко упустить.

Создание новых абстракций

Функции чем-то похожи на кубики Лего. Когда мы строим программу, она состоит из отдельных кубиков, причём каждый из них решает свою задачу.

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

Например, у нас есть два готовых кубика: получить слова из текста, оставить только уникальные слова:

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

Сила композиции в том, что с её помощью можно создавать новые абстракции гораздо проще и удобнее:

Когда мы решим переиспользовать эту деталь и создать на её основе ещё одну более сложную сущность, композиция запросто с этим справится.

В примере ниже мы берём ранее созданную деталь и делаем новую функцию, которая будет не только находить уникальные слова, но ещё и сортировать их по алфавиту.

Если речь идёт о конструировании сложных деталей, вложенную композицию можно заменить на линейную:

Таким образом, композиция — это не просто шаблон для организации потока вычислений, но и фабрика по производству новых деталей.

Бесточечный стиль

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

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

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

В разработке есть две по-настоящему сложные проблемы: инвалидировать кеш и придумать названия для переменных. Композия не поможет решить задачу с кешем, но проблем с именованием параметров точно станет меньше.

Ограничения

Функция возвращает одно значение, следовательно, внутри конвейера или композиции мы можем передать дальше только один аргумент. Но как быть, если функция определена с несколькими параметрами, необходимыми для работы?

Здесь на помощь приходит частичное применение и каррирование, о которых мы поговорим позже.

Пишем сами

Реализовать конвейер можно было бы так:

Чтобы реализовать композицию, достаточно заменить reduce на reduceRight:

Как на практике?

На практике не так много случаев, где можно применить композицию. Кроме того, применимость ограничена отсутствием в JS встроенных механизмов — нужно использовать библиотеки или самостоятельно реализовывать у себя необходимые функции. Но при этом, как ни странно, если вы используете на проекте Redux, ничего подключать не придётся, потому что композиция входит в состав библиотеки.

На проекте с Redux композиция наверняка будет использоваться для middleware, потому что createStore принимает только один усилитель (enhancer), а их, как правило, требуется хотя бы несколько.

Мы помним, что композиция направлена справа налево. Промежуточные слои, которые должны быть вызваны раньше других, помещаются правее или ниже. В примере выше DevTools добавляются до применения middleware, чтобы можно было корректно дебажить асинхронный код.

Другой кейс, где может пригодиться композиция — фильтрация или преобразование потока данных:

Частичное применение и каррирование

Преобразуют функцию с исходным набором параметров в другую функцию с меньшим числом параметров, но делают это по-разному.

Для демонстрации работы частичного применения и каррирования будем использовать такую незамысловатую функцию:

Частичное применение

Преобразует функцию в одну функцию с меньшим числом параметров.

Каррирование

Преобразует функцию в набор функций с единственным параметром.

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

В чём разница?

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

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

Можно провести аналогию: вы делаете заказ в ресторане. Если использовать частичное применение, официант задаст вам только один вопрос о том, что вы хотите заказать, и если вы ответите «хочу пиццу», то остальное он додумает сам и принесёт ту пиццу, которую посчитает нужной.

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

Решение задачи с композицией

Проблему с композицией из предыдущего примера при помощи частичного применения или каррирования можно решить вот так:

Порядок данных

Частичное применение и каррирование чувствительны к порядку данных. Существует два подхода к порядку объявления параметров.

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

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

Специализация

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

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

Пишем сами

Свою версию частичного применения можно написать примерно так:

Каррирование выглядит немного сложнее:

Как на практике?

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

Неизменяемые данные

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

Для иллюстрации принципа работы неизменяемых данных подойдёт пример со стаканом. Представим, что у нас есть стакан с водой, из которого мы немного выпиваем, а через некоторое время делаем ещё один глоток. Стакан опустеет ровно настолько, сколько мы из него выпили. Это — изменяемые данные.

С неизменяемыми структурами данных совершенно другая история. Перед тем, как сделать глоток, создаётся точная копия стакана, и мы пьём уже из копии. Таким образом, после первого глотка у нас будет два стакана: один полный, из другого мы немного отпили. Исходный стакан останется без изменений. Это и есть неизменяемые данные.

Преимущества неизменяемых структур данных:
— предсказуемое изменение состояния,
— быстрое сравнение по ссылке,
— кешируемость,
— легко распараллеливать,
— легко тестировать.

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

Нечаянное мутирование данных

В JavaScript запросто можно нечаянно мутировать массив или любой другой объект:

Мы можем попробовать защититься от этого, но есть проблема. Вещи, которые кажутся неизменяемыми, на самом деле таковыми не являются. Объявление через const защищает от изменений только ссылку, а сам объект остаётся открыт для мутаций.

Все ссылочные типы — объекты, массивы и другие — всегда передаются по ссылке. Во время присваивания или передачи параметра происходит копирование ссылки, но не самих данных.

А что если применить средства метапрограммирования и, например, заморозить объект? В этом случае мы всё равно сможем изменить вложенные объекты по ссылке.

Вместо заморозки можно воспользоваться Proxy API, но в этом случае тоже придётся дополнительно обрабатывать вложенные структуры, потому что они всё ещё открыты для изменений.

В общем, в JavaScript нельзя просто так взять и защитить данные от непреднамеренного изменения.

Затраты на копирование

С копированием данных тоже не всё так просто. В большинстве случаев работает копирование массивов и объектов встроенными средствами JavaScript:

К сожалению, в этом случае создаётся поверхностная копия, поэтому мы избавляемся от мутаций только до тех пор, пока отсутствуют другие вложенные объекты:

Такая же история с функциональными методами массивов — map и filter создают поверхностную копию исходного массива.

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

Неизменяемые структуры данных (persistent data structures)

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

Пожалуй, две самые популярные библиотеки в мире фронтенд-разработки — это Immutable и Immer. При помощи Immer мы можем сделать вот что:

Да, нам всё равно приходится для изменения данных вызывать функцию produce, но это уже лучше, чем рассчитывать на отсутствие случайных мутаций. Кроме того, Immer замораживает все объекты, которые возвращает produce, чтобы защитить разработчика от возможных нечаянных мутаций.

Как на практике?

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

Но, как мне кажется, во многих других ситуациях неизменяемые структуры данных подойдут куда лучше:

Чистые функции (pure functions)

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

Чистые функции могут вызывать другие чистые функции, но если внутри цепочки вызовов попадётся хотя бы одна функция, которая не отвечает условиям чистоты, вся цепочка перестаёт быть чистой. Рассмотрим подробно каждое из условий, которым должны отвечать чистые функции.

Побочные эффекты (side effects)

Побочными эффектами называется любое взаимодействие с внешним миром через операции ввода/вывода (логирование, запись в файл, запрос на сервер и т. д.), изменение глобальных переменных и мутация данных.

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

Работа с глобальными переменными — тоже побочный эффект.

Как правило, изменение глобальных значений непосредственно влияет на текущее состояние приложения, в то время как операции ввода/вывода меняют что-то за пределами приложения. Но в веб-разработке всё вращается вокруг DOM. Это не только доступы и изменение глобальных переменных, но ещё и операции ввода/вывода. Получается, что фронтенд — один сплошной побочный эффект. Другими словами, фронтенд замечателен тем, что совмещает в себе всё «самое лучшее».

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

Мутация данных внутри функции — ещё одна разновидность побочных эффектов. Функция, которая мутирует данные, как бы оставляет след в виде изменений после вызова. Сложность в том, что многие встроенные функции JS по умолчанию мутируют данные. Если об этом забыть, можно нечаянно оставить после вызова функции след из побочных эффектов.

Лучший способ избежать мутации данных — использовать неизменяемые структуры данных.

Зависимость от параметров

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

Внешние зависимости можно заменить на зависимость от параметров.

Непредсказуемый результат

Чистые функции всегда возвращают один и тот же результат для одних и тех же параметров. Как только появляется непредсказуемость, функция теряет чистоту. Простой пример непредсказуемого результата — работа со случайностью.

Чтобы сделать функцию чистой, достаточно вынести неопределённость за пределы функции и передать её в качестве параметра. Например, вот так:

Теперь функция всегда возвращает один и тот же результат для одних и тех же параметров.

Преимущества чистых функций

Их плюсы:
— проще разобраться, как устроена функция,
— их можно запросто кешировать,
— легко тестировать,
— легко распараллеливать.

Кроме того, они обладают ссылочной прозрачностью. Это эффект, который позволяет вместо вызова функции без особых трудностей подставить результат её работы.

Так почему бы всё не написать на чистых функциях?

Абсолютная и относительная чистота

Если взять и написать программу только из чистых функций, то получится:

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

Кроме того, чистота относительна. Функция ниже — чистая или нет?

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

Заключение

Мне кажется, чистые функции — одна из самых полезных и применимых методик, для которой не нужен ни функциональный язык, ни библиотеки. Достаточно по-новому взглянуть на свой код. Неизменяемые данные тоже хороши, но для работы с ними потребуются дополнительные библиотеки. Да и остальные концепции тоже можно использовать, но реже.

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

Кроме того, загляните в репозиторий Awesome FP JS, вдруг найдёте что-то интересное для себя. Если же захочется целиком погрузиться в функциональную парадигму, но при этом продолжать разрабатывать фронтенд, можно посмотреть в сторону Ramda или Elm.

Спасибо за внимание, жду вас в комментариях — будем обсуждать материал и делиться опытом.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *