Почему функциональное программирование такое сложное

Функция zip

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

В данном формате последовательность – это итерируемая последовательность, т.е. список, кортеж, диапазон или строковые данные. Функция возвращает ленивый объект-последовательность, который нужно вычислить, чтобы увидеть результат. Приведенный ниже интерактивный сеанс это демонстрирует:

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

Что такое ООП?

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

Посмотрите на два выражения:

1: f(o); 2: o.f();

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

Никакой семантической разницы явно нет. Вся разница целиком и полностью в синтаксисе. Но одно выглядит процедурным, а другое объектно ориентированным. Это потому что мы привыкли к тому, что для выражения 2. неявно подразумевается особая семантика поведения, которой нет у выражения 1. Эта особая семантика поведения — полиморфизм.

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

С другой стороны, когда мы видим выражение 2. мы видим объект с именем o которому посылают сообщение с именем f. Мы ожидаем, что могут быть другие виды объектов, котоые принимают сообщение f и поэтому мы не знаем, какого конкретно поведения ожидать от f после вызова. Поведение зависит от типа o. то есть f — полиморфен.

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

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

Поэтому то, что действительно отличает ООП программы от не ООП программ это полиморфизм.

Возможно вы захотите возразить, что полифорфизм можно сделать просто используя внутри f switch или длинные цепочки if/else. Это правда, поэтому мне нужно задать для ООП ещё одно ограничение.

Использование полиморфизма не должно создавать зависимости вызывающего от вызываемого.

Чтобы это объяснить, давайте ещё раз посмотрим на выражения. Выражение 1: f(o), похоже зависит от функции f на уровне исходного кода. Мы делаем такой вывод потому что мы также предполагаем, что f только одна и что поэтому вызывающий должен знать о вызываемом.

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

Если конкретнее, то это означает, что модули (файлы с исходным кодом), которые содержат полиморфные вызовы функций не должны ссылаться на модули (файлы с исходным кодом), которые содержат реализацию этих функций. Не может быть никаких include или use или require или каких-то других ключевых слов, которые создают зависимость одних файлов с исходным кодом от других.

Итак, наше редукционистское определение ООП это:

Чистые и неизменяемые функции

В Java есть несколько неизменяемых структур данных:

  • integer;
  • Boolean;
  • byte;
  • short;
  • string.

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

// неизменяемый классpublic final class Student {     final String name;     final int regNo;     public Student(String name, int regNo)     {         this.name = name;         this.regNo = regNo;     }     public String getName()     {         return name;     }     public int getRegNo()     {         return regNo;     } }

Ключевое слово в классе предотвращает создание дочернего класса. Использование для и делает невозможным изменение значений после построения объекта.

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

На практике: функциональное реактивное программирование на JavaScript

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

Для согласования всего, что вы, возможно, читали о FRP в JS, важно понять, что Rx, Bacon.js, Angular и другие не согласуются с двумя основными принципами определения FRP Конала Эллиота

Функциональное реактивное программирование в своей реализации в JavaScript относится к программированию в функциональном стиле при создании и реагировании на потоки. Это довольно далеко от оригинальной формулировки Эллиота (которая специально исключает потоки как компонент), но тем не менее вдохновляется традиционными FRP.

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

Без императивного или нечистого кода веб-приложение JS с пользовательским интерфейсом не было бы очень полезным, поскольку оно не могло бы взаимодействовать со своей средой.

Давайте взглянем на пример, чтобы продемонстрировать основные принципы FRP-вдохновленного JavaScript. Этот пример использует RxJS и печатает движения мыши в течение десяти секунд:

Вы можете проверить этот код в действии в JSFiddle: FRP-вдохновленном JavaScript. Запустите скрипт и, пока идет подсчет до 10, наведите указатель мыши в экран с результатом. Вы должны увидеть координаты мыши вместе со счетчиком. Тогда на экран выведется, где была ваша мышь во время каждого 1-секундного интервала времени.

Давайте кратко обсудим эту реализацию шаг за шагом.

Сначала мы создаем observable . Это таймер, добавляющий значение в коллекцию каждые (каждую секунду). Нам нужно обработать событие таймера, чтобы извлечь его значение и добавить к результирующему потоку.

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

Затем мы хотим объединить потоки и . Для этого используем оператор объединения. Так мы можем определить, какие движения мыши произошли в течение каждого интервала времени. Мы будем называть результирующий observable . Мы также ограничим observable так, чтобы он завершился через десять секунд ().

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

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

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

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

Логическое распознавание типов и чувствительность к пробелам и отступам

F# позволяет объявлять переменные и значения без указания их типов, поэтому можно подумать, будто F# является динамическим языком, но это не так

Важно понимать, что F# такой же статический язык, как C# или C++. Однако F# имеет мощную систему логического распознавания типов (type inference), которая позволяет во многих местах не указывать типы объектов

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

Хотя такие системы логического распознавания типов в действительности отсутствуют в императивных языках, само по себе распознавание типов не связано напрямую с функциональным программированием. Однако распознавание типов — критическая важная концепция, которую нужно понять, если вы хотите освоить F#. К счастью, если вы знаете C#, то наверняка уже знакомы с базовой концепцией логического распознавания типов из-за ключевого слова var:

Обе строки кода на C# создают новые переменные, которые статически типизируются как Dictionary<string, string>, но во втором случае ключевое слово var сообщает компилятору логически определять тип переменной за вас. F# выводит эту концепцию на новый уровень. Например, вот функция add в F#:

В приведенном выше коде нет ни одного указания типа, но F# Interactive раскрывает статическую типизацию:

Смысл стрелок я подробнее объясню потом, а пока вы можете интерпретировать это так:функция add принимает два int-аргумента, а four является значением типа int. Компилятор F# смог логически распознать все типы, исходя из определений add и four. Компилятор использует при этом правила, которые выходят за рамки данной статьи, но, если вас это интересует, вы можете узнать больше в F# Developer Center.

Логическое распознавание типов — один из способов, с помощью которых F# удаляет лишний «шум» из вашего кода, но обратите внимание еще и на отсутствие фигурных скобок и ключевых слов, обозначающих тело функции add или ее возвращаемое значение. А все дело в том, что F# по умолчанию является языком, чувствительным к пробелам и отступам

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

Справочный материал[править]

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

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

  1. Лисп (List processor). Считается первым функциональным языком программирования. Поддерживает динамическую и факультативно статическую типизацию. Содержит массу императивных свойств, однако в общем поощряет именно функциональный стиль программирования. При вычислениях использует вызов-по-значению. В стандарт Common Lisp входит Common Lisp Object System (CLOS) — объектная система Common Lisp, которая по многим параметрам превосходит объектные системы в других языках (поддерживает метаобъектный протокол, мультиметоды и т. д.).
  2. ISWIM (If you See What I Mean). Функциональный язык-прототип. Разработан Питером Ландиным в 60-х годах XX ве́ка для демонстрации того, каким может быть язык функционального программирования. Вместе с языком П. Ландин разработал и специальную виртуальную машину для исполнения программ на ISWIM’е. Эта виртуальная машина, основанная на вызове-по-значению, получила название SECD-машины. На синтаксисе языка ISWIM базируется синтаксис многих функциональных языков. На синтаксис ISWIM похож синтаксис ML, особенно Caml.
  3. Scheme. Диалект Lisp’а, предназначенный для научных исследований в области computer science. При разработке Scheme был сделан упор на элегантность и простоту языка. Благодаря этому язык получился намного меньше, чем Common Lisp.
  4. ML (Meta Language). Семейство строгих языков с развитой полиморфной системой типов и параметризуемыми модулями. ML преподаётся во многих западных университетах (в некоторых даже как первый язык программирования).
  5. Standard ML. Один из первых типизированных языков функционального программирования. Содержит некоторые императивные свойства, такие как ссылки на изменяемые значения и поэтому не является чистым. При вычислениях использует вызов-по-значению. Очень интересная реализация модульности. Мощная полиморфная система типов. Последний стандарт языка — Standard ML-97, для которого существует формальные математические определения синтаксиса, а также статической и динамической семантик языка.
  6. Caml Light и Objective Caml. Как и Standard ML принадлежит к семейству ML. Objective Caml отличается от Caml Light в основном поддержкой классического объектно-ориентированного программирования. Также как и Standard ML строгий, но имеет некоторую встроенную поддержку отложенных вычислений.
  7. Miranda. Разработан Дэвидом Тёрнером, в качестве стандартного функционального языка, использовавшего отложенные вычисления. Имеет строгую полиморфную систему типов. Как и ML преподаётся во многих университетах. Оказал большое влияние на разработчиков языка Haskell.
  8. Haskell. Один из самых распространённых не строгих языков. Имеет очень развитую систему типизации. Несколько хуже разработана система модулей. Последний стандарт языка — Haskell-98.
  9. Gofer (GOod For Equational Reasoning). Упрощённый диалект Haskell’а. Предназначен для обучения функциональному программированию.
  10. Clean. Специально предназначен для параллельного и распределённого программирования. По синтаксису напоминает Haskell. Чистый. Использует отложенные вычисления. С компилятором поставляется набор библиотек (I/O libraries), позволяющих программировать графический пользовательский интерфейс под Win32 или Mac OS.

Литератураправить

  1. Хювёнен Э., Сеппенен И. Мир Lisp’а. В 2-х томах. М.: Мир, 1990.
  2. Бёрдж В. Методы рекурсивного программирования. М.: Машиностроение, 1983.
  3. Филд А., Харрисон П. Функциональное программирование. М.: Мир, 1993.
  4. Хендерсон П. Функциональное программирование. Применение и реализация. М.: Мир, 1983.
  5. Джонс С., Лестер Д. Реализация функциональных языков. М.: Мир, 1991.
  6. Henson  M. Elements of functional languages. Dept. of CS. University of Sassex, 1990.
  7. Fokker J. Functional programming. Dept. of CS. Utrecht University, 1995.
  8. Thompson S. Haskell: The Craft of Functional Programming. 2-nd edition, Addison-Wesley, 1999.
  9. Bird R. Introduction to Functional Programming using Haskell. 2-nd edition, Prentice Hall Press, 1998.

Функция curryN

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

Но что если вместо добавления всего двух чисел функция add могла суммировать все числа, передаваемые в неё в качестве аргументов?

Мы всё ещё можем каррировать её, ограничив количество аргументов с помощью curryN:

Использование curryN для ожидания количества вызовов функции

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

Можем эмулировать такое поведение с помощью curryN.

Примечание: мы будем использовать эту методику при аппликативной валидации.

Существующие решения

Авторы имеющихся решений предлагают два основных варианта. Назовём их применение по возможности и явное применение.

Применение по возможности

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

Достаточно остроумная идея. Но, к сожалению, на практике не работает.

Почему? Допустим, мы хотим частично применить функцию, вычисляющую сумму произвольного количества переменных:

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

Следовательно, предыдущий пример ломается на втором шаге, если вместо подставить :

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

Есть и другая проблема, когда можно «проскочить» правильное количество аргументов. В нашем примере можно изобразить так:

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

Явное применение

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

А именно, создадим перегрузку оператора «скобочки» без аргументов, вызов которого будет означать, что нужно вызвать внутреннюю функцию с теми параметрами, которые уже были частично применены:

Старая проблема решена. Но возникла новая.

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

Arity (арность)

Количество аргументов функции. От слов унарный, бинарный, тернарный (unary, binary, ternary) и так далее. Это необычное слово, потому что состоит из двух суффиксов: «-ary» и «-ity.». Сложение, к примеру, принимает два аргумента, поэтому это бинарная функция, или функция, у которой арность равна двум. Иногда используют термин «диадный» (dyadic), если предпочитают греческие корни вместо латинских. Функция, которая принимает произвольное количество аргументов называется, соответственно, вариативной (variadic). Но бинарная функция может принимать два и только два аргумента, без учета каррирования или частичного применения.

На практике: изменяемость в JavaScript

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

Когда мы говорим о функциональном программировании в JS, слово «неизменяемое» используется много, но разработчик обязан всегда держать ее в голове. Например, Redux полагается на одно неизменяемое дерево состояний. Однако сам JavaScript способен изменять объект состояния. Чтобы реализовать неизменяемое дерево состояний, нам нужно каждый раз при изменении состояния возвращать новый объект состояния.

Для неизменяемости объекты JavaScript также могут быть заморожены с помощью

Обратите внимание, что это «неглубокая» заморозка — значения объектов внутри замороженного объекта все еще могут быть изменены. Для гарантированной неизменяемости такие функции «глубокой» заморозки, как Mozilla deepFreeze() и npm deep-freeze могут рекурсивно замораживать объекты

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

Существуют также библиотеки, поддерживающие неизменяемость в JS. Mori предоставляет постоянные структуры данных на основе Clojure. Immutable.js от Facebook также предоставляет неизменяемые коллекции для JS. Библиотеки утилит, такие как Underscore.js и lodash, предоставляют методы и модули для более функционального стиля программирования (а стало быть направленного на неизменяемость).

Философствования

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

Мысль первая

Частичное применение является отложенным вызовом. Иначе говоря, частичное применение не производит никаких «частичных вычислений». Оно просто запоминает несколько аргументов и ждёт, пока придут остальные.

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

Мысль вторая

В языке C++ нет встроенного механизма частичного применения. Это значит, что программист, вообще говоря, не ожидает, что может какой-то произвольной функции передать только часть аргументов, а остальные «добросить» когда-нибудь потом.

Следовательно, работая с частичным применением, программист заведомо знает, что он его использует.
Значит, частичное применение в языке C++ всегда будет явным.

Результат размышления

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

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

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

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

  • JavaScript. Хотя JavaScript не является чисто функциональным языком, у него есть много функций функционального программирования. Из-за этого программисты чаще, чем раньше, используют функциональное программирование на JavaScript, потому что оно может быть более полезным и стабильным в определённых сценариях по сравнению с объектно-ориентированными языками.
  • Clojure — это не совсем то, что нарицательное имя JavaScript, но это надёжный функциональный язык программирования, построенный на основе Lsip, который существует с 1950-х годов. Это означает, что он функционален до самого основания. Он работает на платформе Java и скомпилирован в байт-код JVM.
  • Haskell — ещё один чисто функциональный язык, разработанный для решения реальных, а не академических задач. Он не так стар, как Lisp, он был создан в 1990-х годах. Он использовался для нескольких популярных проектов, таких как оконный менеджер Xmonad.

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

  • Языки программирования высокого и низкого уровня.
  • Языки объектно-ориентированного программирования.

Javascript

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

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

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

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

Обобщённое программирование

Обобщённое программирование (англ. generic programming) — парадигма программирования,
заключающаяся в таком описании данных и алгоритмов,
которое можно применять к различным типам данных, не меняя само это описание.

В том или ином виде поддерживается разными языками программирования.
Возможности обобщённого программирования впервые появились в виде дженериков (обобщённых функций)
в 1970-х годах в языках Клу и Ада,
затем в виде параметрического полиморфизма в ML и его потомках,
а затем во многих объектно-ориентированных языках, таких как C++, Java, Object Pasca, D, Eiffel,
языках для платформы .NET и др.

Обобщённое программирование — Википедия.

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

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

Наконец, рассмотрим эту цитату из первого издания Eloquent JavaScript: «Fu-Tzu написал небольшую программу, использующую глобальное состояние и сомнительные переплетения, и, прочитав ее, студент спросил:« Вы предупреждали нас против этих методов, но я нахожу их в вашей программе. Как такое могло случиться?». Фу-Цзы ответил: «Нет необходимости забирать водяной шланг, когда дом не горит». .

Дополнительную информацию о функциональном реактивном программировании можно найти на следующих ресурсах:

  • Функциональное реактивное программирование для начинающих
  • Функциональное реактивное заблуждение
  • Haskell — функциональное реактивное программирование
  • Создание реактивной анимации
  • Более элегантная спецификация для FRP
  • Elm — прощание с FRP
  • Ранние успехи и новые направления в функциональном реактивном программировании
  • Разрушение FRP
  • Rx не является FRP

Заключение

Мы закончим еще одной отличной цитатой из первого издания Eloquent JavaScript: «Студент долгое время сидел за своим компьютером, мрачно хмурился и пытался написать красивое решение сложной проблемы, но не мог найти правильный подход. Фу-Цу ударил его по затылку и крикнул: ‘Введите что-нибудь!’. Студент начал писать уродливое решение, и после того, как он закончил, он внезапно понял прекрасное решение».

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

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