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


Оглавление (нажмите, чтобы открыть):

Язык программирования JavaScript: информация для начинающих

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

Языки программирования – это только лишь инструмент, с помощью которого человек строит правила в созданных системах.

Интернет представляет собой массу возможностей, за которые ухватываются светлые и предприимчивые умы. Конечно, веб-разработка тоже имеет свои инструменты для воплощения идей в жизнь. Один из них – язык программирования JavaScript , о котором и пойдёт речь в данной статье:

Общая информация

Многие люди, даже не имеющие никакого отношения к IT-сфере, слышали слово Java . Революционный независимый от платформ язык, на котором активно пишут приложения для мобильных систем. Он был разработан перспективной компанией Sun , которая затем перешла « под крыло » Oracle . Но ни та, ни другая компании не имеют никакого отношения к JavaScript :

От Sun потребовалось лишь разрешение на использование части названия. Удивительно, но JavaScript вообще не принадлежит ни одной фирме.

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

  • Объектно-ориентированность. Выполнение программы представляет собой взаимодействие объектов;
  • Приведение типов данных проводится автоматически;
  • Функции выступают объектами базового класса. Эта особенность делает JavaScript похожим на многие функциональные языки программирования, такие как Lisp и Haskell ;
  • Автоматическая очистка памяти. Так называемая, сборка мусора делает JavaScript похожим на C# или Java .

Если говорить о сути применения JavaScript , то этот язык позволяет « оживлять » неподвижные страницы сайтов с помощью кода, который можно запустить на исполнение ( так называемые, скрипты ). То есть, можно провести аналогию с мультфильмами, где html и css – это прорисованные герои, а JavaScript – это то, что заставляет их двигаться.

Если говорить о синтаксисе JavaScript , то ему присущи следующие особенности:

  • Регистр важен. Функции с названиями func() и Func() – совершенно разные;
  • После операторов необходимо ставить точку с запятой;
  • Встроенные объекты и операции;
  • Пробелы не учитываются. Можно использовать сколько угодно отступов, а также переводов строки, чтобы оформить свой код.

Простейший код на JavaScript выглядит следующим образом:

Сфера применения

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

  • Разработка веб-приложений. Хотите установить простой счетчик, организовать передачу данных между формами или поместить на своем сайте игру? Тогда JavaScript выступит верным помощником в этом деле;
  • «Активное участие» в AJAX . Эта технология позволила значительно ускорить работу приложений, осуществляя обмен данными с сервером в « фоновом » режиме:
  • Операционные системы. Возможно, кто-то не знал, но Windows , Linux и Mac имеют своих браузерных конкурентов, львиная доля кода которых написана на JavaScript ;
  • Мобильные приложения;
  • Сфера обучения. Любая программистская специальность в университете включает в себя изучение JavaScript в том или ином объеме. Это обусловлено тем, что язык изначально разрабатывался для не очень сильных программистов. Уроки JavaScript логически вплетаются в базовый курс HTML , поэтому освоение проходит достаточно просто.

Преимущества и недостатки

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

  • Необходимость обеспечивать кроссбраузерность. Раз уж JavaScript выступает как интернет-технология, то приходится мириться с правилами, которые устанавливает всемирная паутина. Код должен корректно выполняться во всех, или хотя бы самых популярных, браузерах;
  • Система наследования в языке вызывает трудности в понимании происходящего. В JavaScript реализовано наследование, основанное на прототипах. Люди, изучавшие другие объектно-ориентированные языки программирования, привыкли к привычному « класс потомок наследует родительский класс ». Но в JavaScript такими вещами занимаются непосредственно объекты, а это не укладывается в голове;
  • Отсутствует стандартная библиотека. JavaScript не предоставляет никаких возможностей для работы с файлами, потоками ввода-вывода и прочими полезными вещами;
  • Синтаксис в целом затрудняет понимание. Красота кода – явно не конёк JavaScript , но главное правило программистов соблюдено: « Работает? Не трожь! ».

Теперь стоит отметить некоторые преимущества

  • JavaScript предоставляет большое количество возможностей для решения самых разнообразных задач. Гибкость языка позволяет использовать множество шаблонов программирования применительно к конкретным условиям. Изобретательный ум получит настоящее удовольствие;
  • Популярность JavaScript открывает перед программистом немалое количество готовых библиотек, которые позволяют значительно упростить написание кода и нивелировать несовершенства синтаксиса;
  • Применение во многих областях. Широкие возможности JavaScript дают программистам шанс попробовать себя в качестве разработчика самых разнообразных приложений, а это, безусловно, подогревает интерес к профессиональной деятельности.

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

Для тех, кто хочет изучать

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

  • Прежде всего, HTML . Нельзя начинать делать что-либо для интернета без основы основ. Каскадные таблицы стилей ( CSS ) также очень сильно пригодятся;
  • Использовать новую литературу. Программирование – это не физика, законы которой нерушимы, а новые учебные пособия – это урезанные старые. IT-технологии постоянно развиваются, и не стоит пренебрегать полезными обновлениями;
  • Стараться самостоятельно писать все участки программы. Если что-то ну совсем не получается – можно позаимствовать чужой код, но лишь предварительно уяснив для себя каждую строчку;
  • Отладка – ваш верный друг. Быстро находить ошибки – один из важнейших моментов в программировании;
  • Не игнорируйте нормы форматирования. Конечно, код не станет лучше или хуже от разного количества отступов и пробелов, но легкость чтения и понимания программистом – тоже немаловажный момент. Код, приведенный ниже? очень трудно воспринимается, особенно если вы не его автор:
  • Имена переменных должны иметь лексическое значение. В процессе написания простых программ это кажется вовсе не важным, но когда количество строк кода переваливает за тысячу – все черти ломают ноги;
  • Комментируйте. Этот пункт вдогонку к предыдущему;
  • Наблюдайте за опытными людьми. Видеоуроки дают возможность увидеть создание программ. Это даже лучше, чем лекции в университете, ведь запись можно в любой момент остановить и повторить снова;
  • Будьте в курсе всех новостей. Отслеживание всего нового и свежего даст возможность ощущать себя «в гуще событий», а это подталкивает к покорению собственных вершин.

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

Для новичков важно помнить, что язык – это всего лишь инструмент, а главное в программировании – мысль.

Функциональное программирование в javascript [закрыт]

Изучаю Erlang, попробовал написать что-то вроде аналога встроенной функции «свертки» lists:foldl , на javascript:

В этом примере «повышается уровень абстракции», соблюдена чистота функции. Приемлимо ли писать (или пытаться писать) код полностью в функциональном стиле, как в Erlang, на javascript? К чему это может привести в конечном счете?

Закрыт по причине того, что необходимо переформулировать вопрос так, чтобы можно было дать объективно верный ответ участниками user181100, Grundy, aleksandr barakin, user194374, Dmitriy Simushev 28 июл ’16 в 10:42 .

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

Функциональный JavaScript с применением CoffeeScript и Node

Функциональный язык сценариев помогает справляться со сложностью Web-приложений

CoffeeScript — это относительно новый язык, который составляет привлекательную альтернативу для разработчиков, уставших от недостатков JavaScript. CoffeeScript позволяет разработчикам программировать на легком, интуитивно понятном языке, который воспринимается как гибрид Ruby и Python. К тому же CoffeeScript компилирует код JavaScript для Web-приложений на основе браузера и гладко работает с Node.js при создании серверных приложений. Центральной темой этой статьи является третье преимущество использования CoffeeScript ― его работа с функциональной стороной JavaScript. Очищенный, модернизированный синтаксис CoffeeScript открывает мир функционального программирования, скрытый в библиотеках JavaScript.

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

Ни один из основных языков программирования (таких, как Java™, C++ или C#) не является по-настоящему функциональным языком, однако дополнительные библиотеки и конструкции в любом из них обеспечивают различные уровни функционального программирования. Еще важнее то, что такие языки, как Clojure, F# и Erlang, становятся популярными именно потому, что функциональное программирование обещает меньшее количество ошибок и повышенную производительность труда при разработке сложных приложений.

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

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

Обратите внимание, что эта статья продолжает мои предыдущие введения в JavaScript для Java-разработчиков и Node.js для Java-разработчиков. Я предполагаю, что в вашу среду разработки входит Node.js и что вы знакомы с основами программирования в Node.

Настройка CoffeeScript и Node

Если Node.js уже входит в вашу среду разработки, то для установки CoffeeScript можно использовать его менеджер пакетов (NPM). Следующая команда запускает NPM для глобальной установки пакета:

Работа с CoffeeScript главным образом сводится к написанию программ, их сохранению в файлах .coffee и компиляции результатов в JavaScript. Синтаксис CoffeeScript близок к синтаксису JavaScript и должен быть знаком большинству разработчиков; например, в листинге 1 код CoffeeScript выглядит так же, как JavaScript, но без обычного нагромождения скобок и точек с запятой:

Листинг 1. Типичный код CoffeeScript

Команда coffee служит ярлыком некоторых задач управления. Она компилирует файлы CoffeeScript в JavaScript, исполняет файлы CoffeeScript и даже выступает в качестве интерактивной среды или REPL (подобно irb в Ruby).

Вот так я помещаю свой сценарий в файл:

Затем компилирую (или преобразую) этот файл в JavaScript:

Результатом становится файл hello.js. Поскольку результирующий JavaScript допустим для Node, я могу быстро запустить его в своей среде Node (листинг 2).

Листинг 2. JavaScript в среде Node

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

Листинг 3. CoffeeScript в среде Node

Применение watchr

Сообщество сторонников открытого исходного кода создало ряд удобных утилит для работы с файлами (watchers), которые выполняют тесты, компилируют код и т.п. Обычно они работают через командную строку и очень легковесны. Мы настроим инструмент watcher для наблюдения за всеми файлами .coffee в нашей среде разработки и их компиляции в .js после сохранения.

Для этой цели я выбрал утилиту watchr , которая представляет собой библиотеку Ruby. Чтобы использовать watchr , нужны Ruby и RubyGems. Установив их в свою среду разработки, вы сможете выполнить следующую команду для установки watchr как глобальной библиотеки Ruby (включая соответствующие утилиты):

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

Обратите внимание, что команда coffee в данном случае помещает результирующие файлы .js в каталог js .

Это можно сделать в окне терминала, например, так:

Теперь всякий раз, когда я вношу изменения в любой файл .coffee в своем каталоге src , watchr создает новый файл .js и помещает его в каталог js .

CoffeeScript на высоте 10000 футов

CoffeeScript предоставляет много полезных функций, которые значительно облегчают работу по сравнению с JavaScript. На верхнем уровне CoffeeScript исключает необходимость в фигурных скобках, точках с запятой, ключевых словах var и function . Одна из моих любимых особенностей CoffeeScript ― его определение функции, приведенное в листинге 4.

Листинг 4. Функции CoffeeScript ― это просто!

Здесь я объявляю простую функцию для написания слова CoffeeScript с прописной буквы. В CoffeeScript определению функции предшествует стрелка. Тело определения отделено отступом, поэтому в CoffeeScript не нужны фигурные скобки. Обратите также внимание на отсутствие круглых скобок. word.slice 1 из CoffeeScript просто компилируется в word.slice(1) для JavaScript. Тело функции отделено интервалом: весь код под строкой определения функции следует за пустой строкой. console.log без отступа внизу означает, что определение метода завершено. (Две эти особенности перенесены в CoffeeScript из Ruby и Python соответственно.)

Аналогичная функция JavaScript выглядела бы как в листинге 5.

Листинг 5. Даже в однострочном коде JavaScript много мусора

Переменные

CoffeeScript автоматически помещает ключевое слово var JavaScript перед любой определенной вами переменной. Так что при программировании на CoffeeScript не нужно помнить про var . (В JavaScript var ― необязательное ключевое слово. Но без него переменная становится глобальной, что почти всегда нехорошо.)

CoffeeScript также позволяет определить значения параметров по умолчанию, как показано в листинге 6.

Листинг 6. Значения параметров по умолчанию!

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

Листинг 7. Замусоренный JavaScript

Условные операторы

CoffeeScript обрабатывает условные операторы с помощью таких ключевых слов, как and , or и not , как показано в листинге 8.

Листинг 8. Условные операторы CoffeeScript

В листинге 8 для проверки существования объекта я использовал оператор ? . Прежде чем попытаться написать слово с прописной буквы, этот сценарий проверяет, что параметр word не равен null и что его тип ― string . Очень хорошо, что CoffeeScript позволяет использовать is вместо == .

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

JavaScript не поддерживает классы напрямую; это прототипно-ориентированный язык. Тех, кто воспитан на объектно-ориентированном программировании, это может показаться неудобным — нам нужны наши классы! CoffeeScript обеспечивает синтаксис class с целым рядом функций, которые после компиляции в стандартный JavaScript становятся функциями, определенными внутри функций.

В листинге 9 ключевое слово class используется для определения класса Message .

Листинг 9. CoffeeScript ― это классно!

В листинге 9 я использую ключевое слово constructor для определения конструктора. Затем я определяю метод ( asJSON ), указав имя после функции.

CoffeeScript и Node

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

Листинг 10. Web-приложение Node.js на JavaScript

Переписав то же Web-приложение на CoffeeScript, мы выметаем синтаксический мусор из обратных вызовов Node, как показано в листинге 11.

Листинг 11. CoffeeScript упрощает Node.js

В листинге 11 я добавил оператор or , вместо оператора JavaScript || . Я также обнаружил, что стрелку, указывающую на анонимную функции в app.listen , набрать легче, чем function() .

CoffeeScript говорит со мной

Вероятно, вы уже поняли, что CoffeeScript предпочитает абстрактным символам разговорный английский. Вместо !== CoffeeScript позволяет использовать интуитивно понятное isnt ; а === аналогичным образом превращается в is .

Если выполнить этот файл командой coffee -c , вы увидите, что CoffeeScript выдает почти точную копию кода JavaScript, приведенного в листинге 10. Идеальный JavaScript, выдаваемый CoffeeScript, работает с любой библиотекой JavaScript.

Функциональные коллекции и Underscore

Задуманная в качестве функционального пояса с инструментами для программирования на JavaScript, библиотека функций Underscore.js облегчает JavaScript-разработку. Среди прочего Underscore предлагает ряд ориентированных на коллекции функций, каждая из которых идеально подходит для решения конкретной задачи.

Например, предположим, что необходимо найти все нечетные числа в коллекции чисел, скажем, от 0 до 10, исключительно. Применение CoffeeScript вместе с Underscore позволит сэкономить время на наборе текста и вероятно, убережет от некоторых ошибок. В листинге 12 я привожу основной алгоритм, в то время как Underscore обеспечивает функцию агрегирования, в данном случае filter .

Листинг 12. Функция filter из библиотеки Underscore

Прежде всего, так как знак _ (то есть underscore — подчеркивание) является допустимым именем переменной, я сделал его ссылкой на библиотеку Underscore. Далее, я добавляю к функции filter , которая проверяет числа на нечетность, анонимную функцию. Обратите внимание, что я использовал ключевое слово CoffeeScript isnt , а не !== из JavaScript. Для указания того, что нужно отсортировать числа от 0 до 9, я использовал функцию range . Иначе, я мог бы подсчитать шаги для своего диапазона (считая парами) и начать с любого числа.

Функция filter возвращает массив, который представляет собой отфильтрованную версию входных данных, в данном случае, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] . Так что выполнение кода из листинга 12 дало бы [1, 3, 5, 7, 9] .

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

Листинг 13. Функция map из библиотеки Underscore

Здесь результатом будет [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] Underscore просто добавляет единицу к каждому значению из моего диапазона numbers , так что не нужно перебирать значения вручную.

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

Листинг 14. Функция even из библиотеки Underscore

Определив функцию even , я могу легко соединить ее с такими функциями Underscore, как all и any . В данном случае функция all применяет мою функцию even к каждому значению из диапазона numbers . Затем она возвращает логическое значение, указывающее, являются ли все числа четными (ложно). Аналогично, функция any возвращает логическое значение, указывающее, является ли какое-нибудь число четным (истинно).

Underscore может больше

Здесь я всего лишь касаюсь поверхности возможностей Underscore. В число других трюков входят связки функций, шаблоны JavaScript и глубокая проверка равенства. (См. раздел Ресурсы).

А что, если к коллекции значений не нужно применять никакие из этих функций, а требуется что-то еще? Без проблем! Для этого есть функция Underscore each . Она действует как простой итератор (то есть подспудно управляет логикой цикла, при каждой итерации выполняя указанные действия). Эта функция должна быть знакома тем, кто работал с Ruby или Groovy.

Листинг 15. Функция each из библиотеки Underscore

В листинге 15 функция each принимает коллекцию (мой диапазон numbers ) и функцию, которую следует выполнить с каждым значением из перебираемого массива. В данном случае я использую each для вывода значения текущей итерации на консоль. Так же легко можно было бы сделать что-нибудь вроде сохранения данных в базе данных, возврата результатов пользователю и т.п.

Заключение

CoffeeScript придает JavaScript-программированию свежесть и легкость, что делает его чрезвычайно интересным, особенно для тех, кто знаком с Ruby или Python. В этой статье я показал, как CoffeeScript берет что-то от каждого из этих языков, облегчая чтение кода в стиле JavaScript и значительно ускоряя работу. Сочетание CoffeeScript, Node и Underscore создает невероятно легкий и приятный набор инструментов разработки для основных сценариев функционального программирования. Со временем и практикой приобретенные здесь знания можно легко распространить на более сложные бизнес-приложения, которые опираются на динамическое взаимодействие Web- и мобильных систем.

Ресурсы для скачивания

Похожие темы

  • Оригинал статьи: Functional JavaScript with CoffeeScript and Node.
  • В набор инструментов разработки, продемонстрированный в этой статье, входят:
    • CoffeeScript;
    • Node.js;
    • Underscore.js;
    • Watchr;
    • Ruby;
    • Ruby Gems.
  • Предыдущие статьи из этой серии на портале developerWorks: Java development 2.0: Вторая волна разработки Java-приложений: JavaScript для разработчиков Java (Эндрю Гловер, апрель 2011 г.) и Node.js for Java developers (Эндрю Гловер, ноябрь 2011 г. — EN).
  • Цикл статей Функциональное мышление Нила Форда выводит за пределы синтаксиса функционального программирования в область основополагающих концепций с примерами, написанными на языках Groovy, Scala и Java.
  • Подробнее о CoffeeScript:
    • Ваша первая чашечка CoffeeScript : Часть 1. Приступаем к работе (Майкл Галпин, developerWorks, декабрь 2011 г.);
    • Серия подкастов, посвященных технологии Java: Райан Макгери (Эндрю Гловер, developerWorks, февраль 2011 г. — EN).
  • Поиск ответов на свои вопросы по Node начните отсюда: Что такое Node.js? (Майкл Абернети, developerWorks, май 2011 г. — EN).
  • Java development 2.0: — цикл статей Эндрю Гловера, посвященных технологиям, меняющим представление о Java-разработке.

Комментарии

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

Функциональный JavaScript

Осторожно! Слабонервным, холерикам и беременным не читать — выносит мозг, меняет сознание!

Переменные

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

С появлением переменной появляется зависимость во времени до и после присваивания. Проблема в том что переменные приносят в систему понятие состояния. Как правило каждое состояние приносит нам по два метода для роботы с ним, классический пример, malloc и free , open и close , getCanvas и freeCanvas и т.д.

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

Пример с сменой состояния:

Во-первых, в примере видно плохие имена переменных �� во-вторых, видно, как функция обращается к переменной s и меняет её состояние. Так что будет, если переменная s будет иметь значение 2? Да, верно программа «вылетит» с исключением, но только в текущем event loop. Следующий код, который будет читать значения s и q , получит значения в не консистентном состоянии, т.е. другие части программы могут не ожидать таких значений и могут работать неверно.

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

Объявление функций

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

Объявление функции сродное объявлению обычной переменной:

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

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

Вызов функций

Имея функцию, сохранённую в переменной или просто определенную в текущей или доступной области видимости, её можно использовать собственно для чего она и предназначалась, иными словами вызвать её с необходимыми параметрами и получить ожидаемый результат. Есть несколько способов, как это можно сделать. Первый метод собственно классический: используя скобки funcName(x, y) , где funcName имя функции а x и y передаваемые параметры (аргументы). Собственно результат своей работы функция возвращает «левее», и если его не присвоить переменной или сразу передать как аргумент другой функции, то он будет утрачен.

В JavaScript функция является по сути объектом и имеет некоторые дополнительные методы. Первый из них это метод call .

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

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

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

В даном примере видно как при помощи метода bind была создана новая функция, привязанная к null объекту и также продемонстрировано, как можно сделать частичную передачу параметров. Т.е. привязали аргумент 1 к функции.

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

Контекст

В языке Си есть такое понятие как структуры ( struct ) они удобны тем, что позволяют из простых типов создать сложную структуру с полями, через которые можно обратится к значению.

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

К тому же, всегда первым параметром выступала структура (точнее указатель на неё), которую нужно изменить. И тогда изобрели «классы». В общем, добавили возможность в структуру добавлять и функции (методы), при этом при вызове функции ей автоматически передавался указатель на текущий экземпляр и стали именовать его this .

Классы имеют и много других плюшек, но они нам теперь не интересны. Какое это имеет отношение к JavaScript? Очень даже прямое. Авторы JavaScript тоже решили так сделать, только вот в JavaScript нет классов (традиционных), зато здесь есть объекты. Вот авторы и решили, что каждая функция всегда имеет «переменную» this , которая указывает на текущий объект. Если объекта нет, то указывает на глобальный (в браузерах window ), что иногда приводит к немного печальным последствиям (поэтому в новых версиях поведение немного изменили , привет use strict ). Вот как это выглядит в JavaScript:

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

Как поведёт себя функция скопированная у другого объекта? Верно! Изменится состояние secondObj.name , так как контекст функции будет значение secondObj . Иногда есть надобность передать функцию как аргумент в другую функцию.

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

Мастер Йода рекомендует:  Шифрование хранимых данных в MySQL 5.7

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

В примере так же продемонстрировано, что результат, который возвращает функция, можно использовать сразу. Т.е. в данном примере метод bind возвращает новую функцию, которая сразу же вызывается. Аналогично можно делать с объектами, если функция возвращает объект можно сразу его использовать, как собственно сделано в jQuery. Данный подход называется цепочка вызовов (chaining) и бывает очень полезен, но в функциональном подходе его заменяет композиция.

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

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

Что делать, если нам часто надо суммировать одно число к другому, и при том одно число всегда одно и то же? Нужно где то запомнить это значение. Один из вариантов это использовать замыкание и сделать так:

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

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

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

Вот таким вон нехитрым способом можно уменьшить количество параметров в функциях.

Сигнатура функций

В то время, как JavaScript является языком с динамической типизацией, нет необходимости указывать тип переменной. Это позволяет писать более абстрактный код, хотя временами трудно понять, что ожидает функция и что возвращает. Иногда это приводит к «неожиданным» результатам. Для того, чтобы помочь программисту быстрее ориентироваться в функциях есть несколько подходов, как описать сигнатуры функций. Ах, да, сигнатура функций это как бы описание того что функция получает (аргументы) и то что возвращает.

Первый и самый не интересный нам подход, который используется в ActionScript (часть семейства ECMAScript). В нем используется типизация и параметры описываются более явно, пример:

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

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

И последний вариант, на котором и остановимся. Предназначен он для описания только самых сигнатур и не более. Не многословен, и нет поддержки в IDE (пока нет). Начнём с того, что он «украден» в правильных функциональных языков где каррирование у всех функций по умолчанию. Поэтому далее будем рассматривать все функции как каррированые. Пример сигнатуры:

Как видно с примера, функция работает с четырьмя параметрами первые три это собственно входные параметры, а четвёртый это результат работы. Ах, да важное замечание: в функциональных языках функции всегда возвращают что-нибудь. Если функция ничего не возвращает, то по факту она ничего не делает. и да, не забываем что в идеале функции чистые, т.е. ничего не изменяют (DOM, ввод-вывод, AJAX и т.п.). Функции также не должны изменять значения, которые переданы как аргументы. Если все же есть необходимость их изменить, тогда нужно создать копию оригинального значения и изменить его. Да, это немного расточительно по отошению к памяти, но более безопасно. Ещё пример:

Более сложный пример:

Думаю, этот случай надо более детально рассмотреть. Типов a и b не существует в природе JavaScript, это просто мнемоническое обозначение что тип может быть любой. Квадратные скобки вокруг означают, что это массив элементов a . Так же с последнего примера видно (a -> b) , что первый аргумент функции есть функция в которую будет передан один элемент с массива [a] , и она должна вернуть значение b , которое впоследствии будет помещено в массив элементов [b] . Несколько примеров для самостоятельного изучения:

Композиция

Композиция — процесс применения одной функции к результату другой.

Нечто похожее существует уже довольно давно в *nix системах и называется конвеером (pipe).

Смысл здесь в том, что для достижения конечного результата используется несколько маленьких утилит, которые делают маленький, но необходимый кусок работы. Как в этом примере, одна утилита читает текстовый файл cat file.txt и выводит на экран, но вывод на экран перехвачен и направлен на вход другой утилите grep ^boo , которая в свою очередь фильтрует полученные данные и опять выводит на экран, где они которые опять перехватываются и передаются на вход утилите foo , которая делает с ними тоже что-то светлое и хорошее, и после снова выведет на экран но на этот раз уже успешно, так как вывод не перенаправлен, поэтому пользователь видит результат работы.

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

Очень важно отметить, что композиция/декомпозиция способствует более абстрактному коду. Абстракция в свою очередь способствует быстрой разработке, (но!) увеличивает время на обучение. Другими словами менее опытные разработчики будут тратить больше времени на обучение.

Функтор

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

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

И вот последний еще более приземленный пример:

Построитель вычислений aka «Монады»

Hey Underscore, You’re Doing It Wrong!

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

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

Первое что нужно отметить мы сделали её каррированой и второе поменяли местами аргументы. И если теперь сделаем так:

У нас будет функция, которой можно передать любой массив (или не очень массив а все у чего есть метод filter ) и она вернёт фильтрованный. Думаю, её мощь пока не очень видна — тогда немного усложним.

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

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

Более того с таким подходом можно делать и асинхронные операции:

Промисы? Колбэки? Асинхронный код? Не, не слышал. Но вот тут есть небольшая проблема: а что если сервер недоступен? Или данные должен был ввести пользователь, а их нет? Ой, беда.

Maybe

Хотя если подумать не такая уже и беда, все можно решить в функциональном стиле. Для этого в нас есть (будут?) монады! Посмотрим, как можно определить монаду maybe:

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

Ой, сколько же в нас появилось map ов, для чего же их столько нужно? Ну что ж, попробуем разобраться. Все дело в том, что maybe возвращает контейнер с данными (в даном случае пускай будет будет массив), и чтоб их извлечь, мы используем метод map . Извлечённые данные фильтруем. Далее, после извлечения и фильтрации, данные опять запаковываются, и опять получаем монаду, поэтому перед обработкой данных опять извлекаем данные из монады, а после и из массива. Обрабатываем, результат опять запаковывается дважды (в массив, и в монаду/контейнер). И последняя команда: собственно перед выводом массива извлекаем из монады и передаём обработчику.

Представим случай, что данные мы не получили, т.е. в maybe ничего не передали, при каждом map для монады просто будет проверка, есть ли данные (а их нет), поэтому обработчики ( filterOdd , map(processData) . ) и не будут выполнены.

Either

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

Напишем короткую функцию для демонстрации:

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

Обращаю внимание на то, что теперь метод getElement вынесен из композиции, так как ему на вход нужно «чистое» значение, а не монада, и возвращает он монаду.

Даже если не использовать функциональный подход, то использовать either или maybe в разработке может поднять стабильность на новый уровань. Всегда возаращая манады в качестве результата можно убрать кучу условий которые проверяют есть ли резульат от функции, а также можно забыть про try catch (которым часто злоупотребляют и оборачивают большие куски кода, и не обрабатывают пойманое исключение). Такой себе NullObject паттерн. Для удобства монадам можно добавить методы для получения данных, например, getOrElse , где единственным аргументом будет занчение по умолчанию, на случай если монада «пустая». Или метод getOr , где аргументом будет функция которая будет исполнена в случае отсутствия данных в монаде и результат в виде результата из этой функции.

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

Функциональное программирование (ФП) может улучшить ваш подход к написанию кода. Но ФП непросто освоить. Многие статьи и руководства не уделяют внимания таким подробностям, как монады (Monads), аппликативность (Applicative) и т. д., не приводят в качестве иллюстраций практические примеры, которые могли бы помочь нам в повседневном использовании мощных ФП-методик. Я решил исправить это упущение.

Хочу подчеркнуть: в статье сделан упор на том, ЗАЧЕМ нужна фича Х, а не на том, ЧТО такое фича Х.

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

ФП — это стиль написания программ, при котором просто комбинируется набор функций. В частности, ФП подразумевает обёртывание в функции практически всего подряд. Приходится писать много маленьких многократно используемых функций и вызывать их одну за другой, чтобы получить результат вроде (func1.func2.func3) или комбинации типа func1(func2(func3())).

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

Проблемы ФП

Если всё можно сделать путём комбинирования набора функций, то…

  1. Как обрабатывать условие if-else? (Подсказка: монада Either)
  2. Как обрабатывать исключения Null? (Подсказка: монада Maybe)
  3. Как удостовериться, что функции действительно многократно используемые и могут использоваться везде? (Подсказка: чистые функции (Pure functions), ссылочная прозрачность (referential transparency))
  4. Как удостовериться, что данные, передаваемые нами в функции, не изменены и могут использоваться и в других местах? (Подсказка: чистые функции (Pure functions), неизменяемость)
  5. Если функция берёт несколько значений, но при объединении в цепочку (chaining) можно передавать только по одной функции, то как нам сделать эту функцию частью цепочки? (Подсказка: каррирование (currying) и функции высшего порядка)
  6. И многое другое .

ФП-решение

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

Спецификации Fantasy Land и ФП-библиотеки

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

Яркий пример — спецификации Fantasy Land, объясняющие, как должна себя вести каждая JS-функция/класс.

На иллюстрации изображены все спецификации и их зависимости. Спецификации — по сути законы, они аналогичны интерфейсам в Java. С точки зрения JS спецификации можно рассматривать как классы или функции-конструкторы, реализующие в соответствии со спецификацией некоторые методы (вроде map, of, chain и т. д.).

JS-класс — это функтор (Functor), если он реализует метод map. И метод должен работать так, как предписано спецификацией (объяснение упрощённое, правил на самом деле больше).


JS-класс — это функтор Apply (Apply Functor), если он в соответствии со спецификацией реализует функции map и ap.

JS-класс — это монада (Monad Functor), если он реализует требования Functor, Apply, Applicative, Chain и самой Monad (в соответствии с цепочкой зависимостей).

Примечание: зависимость может выглядеть как наследование, но необязательно. Например, монада реализует обе спецификации — Applicative и Chain (в дополнение к остальным).

Библиотеки, совместимые со спецификациями Fantasy Land

Есть несколько библиотек, реализующих спецификации FL. Например: monet.js, barely-functional, folktalejs, ramda-fantasy (на базе Ramda), immutable-ext (на базе ImmutableJS), Fluture и др.

Какие библиотеки мне лучше использовать?

Библиотеки наподобие lodash-fp и ramdajs позволят только начать писать в стиле ФП. Но они не предоставляют функции для использования ключевых математических концепций вроде монад, функторов или редьюсера (Foldable), позволяющих решать реальные проблемы.

Так что я бы вдобавок порекомендовал выбрать одну из библиотек, использующих спецификации FL: monet.js, barely-functional, folktalejs, ramda-fantasy (на базе Ramda), immutable-ext (на базе ImmutableJS), Fluture и т. д.

Примечание: я пользуюсь ramdajs и ramda-fantasy.

Итак, мы получили представление об основах, теперь перейдём к практическим примерам и изучим различные возможности и методики ФП.

Пример 1. Работа с проверками на Null

В разделе рассматриваются: функторы, монады, монады Maybe, каррирование

Применение: мы хотим показывать разные начальные страницы в зависимости от пользовательской настройки предпочтительного языка. Нужно написать getUrlForUser, возвращающий соответствующий URL из списка URL’ов (indexURL’ы) для пользовательского (joeUser) предпочтительного языка (испанский).

Проблема: язык не может быть null. И пользователь тоже не может быть null (не залогинен). Языка может не быть в нашем списке indexURL’ов. Поэтому нам нужно позаботиться о многочисленных nulls или undefined.

Решение (императивное против функционального):

Не переживайте, если ФП-версия выглядит трудной для понимания. Дальше в этой статье мы разберём её шаг за шагом.

Давайте сначала разберём ФП-концепции и методики, использованные в этом решении.

Функторы

Любой класс (или функция-конструктор) или тип данных, хранящий значение и реализующий метод map, называется функтором (Functor).

Например: массив — это функтор, потому что он может хранить значения и имеет метод map, позволяющий нам применить (map) функцию к хранимым значениям.

Напишем собственный функтор — MyFunctor. Это просто JS-класс (функция-конструктор), который хранит какое-то значение и реализует метод map. Этот метод применяет функцию к хранимому значению, а затем из результата создаёт новый Myfunctor и возвращает его.

P. S. Функторы должны реализовывать и другие спецификации (Fantasy-land) в дополнение к map, но здесь мы этого касаться не будем.

Монады

Монады тоже функторы, т. е. у них есть метод map. Но они реализуют не только его. Если вы снова взглянете на схему зависимостей, то увидите, что монады должны реализовывать разные функции из разных спецификаций, например Apply (метод ap), Applicative (методы ap и of) и Chain (метод chain).

Упрощённое объяснение. В JS монады — это классы или функции-конструкторы, хранящие какие-то данные и реализующие методы map, ap, of и chain, которые что-то делают с хранимыми данными в соответствии со спецификациями.

Вот образец реализации, чтобы вы понимали внутреннее устройство монад.

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

Монада Maybe

Монада Maybe — это класс, реализующий спецификацию монады. Но особенность монады в том, что она корректно обрабатывает значения null или undefined.

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

В коде ниже представлена ramda-fantasy реализация монады Maybe. В зависимости от значения она создаёт экземпляр одного из двух разных подклассов — Just или Nothing (значение или полезное, или null/undefined).

Хотя методы Just и Nothing одинаковы (map, orElse и т. д.), Just что-то делает, а Nothing не делает ничего.

Обратите особое внимание на методы map и orElse в этом коде:

Давайте посмотрим, как можно использовать монаду Maybe для работы с проверками на null.

  1. Если есть какой-то объект, который может быть null или иметь null-свойства, то создаём из него объект-монаду.
  2. Применим библиотеки наподобие ramdajs, которые используют Maybe для доступа к значению изнутри и снаружи монады.
  3. Предоставим значение по умолчанию, если реальное значение окажется null (т. е. заранее обработаем null-ошибки).

Currying (помогает работать с глобальными данными и мультипараметрическими функциями)

В разделе рассматриваются: чистые функции (Pure functions) и композиция (Composition)

Если мы хотим составить цепочки функций — func1.func2.func3 или (func1(func2(func3())), то каждая из них может брать лишь по одному параметру. Например, если func2 берёт два параметра — func2(param1, param2), то мы не сможем включить её в цепочку!

Но ведь на практике многие функции берут по несколько параметров. Так как же нам их комбинировать? Решение: каррирование (Currying).

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

Кроме того, каррирование можно использовать при обращении к глобальным переменным, т. е. делать это «чисто».

Посмотрим снова на наше решение:

Пример 2. Работа с кидающими ошибки функциями и выход немедленно после возникновения ошибки

В разделе рассматривается: монада Either

Монада Maybe очень удобна, если у нас есть значения «по умолчанию» для замены Null-ошибок. Но что насчёт функций, которым действительно нужно кидать ошибки? И как узнать, какая функция кинула ошибку, если мы собрали в цепочку несколько кидающих ошибки функций? То есть нам нужен быстрый отказ (fast-failure).

Например: у нас есть цепочка func1.func2.func3…, и если func2 кинула ошибку, то нужно пропустить func3 и последующие функции и правильно показать ошибку из func2 для дальнейшей обработки.

Монада Either

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

Применение: в приведённом ниже императивном коде мы вычисляем tax и discount для item’ов и отображаем showTotalPrice.

Обратите внимание, что функция tax кинет ошибку, если значение цены будет нечисловое. По той же причине кинет ошибку и функция discount. Но, кроме того, discount кинет ошибку, если цена item’а меньше 10.

Поэтому в showTotalPrice проводятся проверки на наличие ошибок.

Посмотрим, как можно улучшить showTotalPrice с помощью монады Either и переписать всё в ФП-стиле.

Монада Either предоставляет два конструктора: Either.Left и Either.Right. Их можно считать подклассами Either. Left и Right — это монады! Идея в том, чтобы хранить ошибки/исключения в Left, а полезные значения — в Right. То есть в зависимости от значения создаём экземпляр Either.Left или Either.Right. Сделав так, мы можем применить к этим значениям map, chain и т. д.

Хотя и Left, и Right предоставляют map, chain и пр., конструктор Left только хранит ошибки, а все функции реализует конструктор Right, потому что он хранит фактический результат.

Теперь посмотрим, как можно преобразовать наш императивный код в функциональный.

Этап 1. Обернём возвращаемые значения в Left и Right.

Примечание: «обернём» означает «создадим экземпляр какого-то класса». Эти функции внутри себя вызывают new, так что нам не придётся это делать.

Этап 2. Обернём исходное значение в Right, потому что оно валидное и мы можем его комбинировать (compose).

const getItemPrice = (item) => Right(item.price);

Этап 3. Создадим две функции: одну для обработки ошибки, вторую для обработки результата. Обернём их в Either.either (из ramda-fantasy.js api).

Either.either берёт три параметра: обработчика результата, обработчика ошибки и монаду Either. Either каррирована, поэтому мы можем передать обработчики сейчас, а Either (третий параметр) — позже.

Как только Either.either получает все три параметра, она передаёт Either либо в обработчик результата, либо в обработчик ошибки, в зависимости от того, чем является Either — Right или Left.

Этап 4. Используем метод chain для комбинирования функций, кидающих ошибки. Передадим их результаты в Either.either (eitherLogOrShow), которая позаботится о том, чтобы ретранслировать их в обработчик результата или ошибки.

Соберём всё вместе:

Пример 3. Присвоение значения потенциальным Null-объектам

***Использована ФП-концепция: аппликативность (Applicative)

Применение: допустим, нужно дать пользователю скидку, если он залогинился и если действует промоакция (т. е. существует скидка).

Воспользуемся методом applyDiscount. Он может кидать null-ошибки, если пользователь (слева) или скидка (справа) является null.

Посмотрим, как можно решить это с помощью аппликативности.

Аппликативность (Applicative)

Любой класс, имеющий метод ap и реализующий спецификацию Applicative, называется аппликативным. Такие классы могут использоваться в функциях, которые работают с null-значениями как с левой стороны (пользователь) уравнения, так и с правой (скидка).

Монады Maybe (как и все монады) тоже реализуют спецификацию ap, а значит, они тоже аппликативные, а не просто монады. Поэтому на функциональном уровне мы можем использовать монады Maybe для работы с null. Посмотрим, как заставить работать applyDiscount с помощью монады Maybe, используемой как аппликативная.

Этап 1. Обернём потенциальные null-значения в монады Maybe.

Этап 2. Перепишем функцию и каррируем её, чтобы передавать по одному параметру за раз.

Этап 3. Передадим через map первый аргумент (maybeUser) в applyDiscount.

Иными словами, теперь у нас есть функция, обёрнутая в монаду!

Этап 4. Работаем с maybeApplyDiscountFunc.

На этом этапе maybeApplyDiscountFunc может быть:

1) функцией, обёрнутой в Maybe, — если пользователь существует;
2) Nothing (подклассом Maybe) — если пользователь не существует.

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

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

Ой-ёй: map не знает, как выполнять функцию (maybeApplyDiscountFunc), когда она сама внутри Maybe!

Поэтому нам нужен другой интерфейс для работы по такому сценарию. И этот интерфейс — ap!

Этап 5. Освежим информацию о функции ap. Метод ap берёт другую монаду Maybe и передаёт/применяет к ней хранимую им в данный момент функцию.

Можем просто применить (ap) maybeApplyDiscountFunc к maybeDiscount вместо использования map, как показано ниже. И это будет прекрасно работать!

Для сведения: очевидно, в спецификации Fantasy Land внесли изменение. В старой версии нужно было писать: Just(f).ap(Just(x)), где f — это функция, х — значение. В новой версии нужно писать Just(x).ap(Just(f)). Но реализации по большей части пока не изменились. Спасибо keithalexander.

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

Функция curryN

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

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

const add = (. args) => R.sum(args); //Суммирует все числа в аргументах

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

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

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

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

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

Пример 4. Сбор и отображение ошибок

В разделе рассматривается: валидация (известна как функтор Validation, аппликативность Validation, монада Validation).

Валидациями обычно называют аппликативность Validation (Validation Applicative), потому что она чаще всего применяется для валидации с использованием функции ap (apply).

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

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

Применение: у нас есть форма регистрации, которая проверяет имя, пароль и почту с помощью трёх функций (isUsernameValid, isPwdLengthCorrect и ieEmailValid). Нам нужно одновременно показать все три ошибки, если они возникнут.

Для этого воспользуемся функтором Validation. Посмотрим, как это можно реализовать с помощью аппликативности Validation.

Возьмём из folktalejs библиотеку data.validation, в ramda-fantasy она ещё не реализована. По аналогии с монадой Either у нас есть два конструктора: Success и Failure. Это подклассы, каждый из которых реализует спецификации Either.

Этап 1. Для использования валидации нам нужно обернуть правильные значения и ошибки в конструкторы Success и Failure (т. е. создать экземпляры этих классов).

Повторите процесс для всех проверочных функций, кидающих ошибки.

Этап 2. Создадим пустую функцию (dummy function) для хранения успешного статуса проверки.

const returnSuccess = () => ‘success’;//просто возвращает success

Этап 3. Используем curryN для многократного применения ap

С ap есть проблема: левая часть должна быть функтором (или монадой), содержащим функцию.

Допустим, нам нужно многократно применить ap. Это будет работать, только если monad1 содержит функцию. И результат monad1.ap(monad2), т. е. resultingMonad, тоже должен быть монадой с функцией, чтобы можно было применить ap к monad3.

В общем, чтобы дважды применить ap, нам нужны две монады, содержащие функции.

В данном случае у нас три функции, к которым нужно применить ap.

Допустим, мы сделали что-то такое.

Предыдущий код не станет работать, потому что результатом Success(returnSuccess).ap(isUsernameValid(username)) будет значение. И мы не сможем применить ap ко второй и третьей функциям.

Можно использовать curryN, чтобы возвращать функцию до тех пор, пока она не будет вызвана N раз.

Теперь каррированная success возвращает функцию три раза.

Javascript и функциональное программирование: полноправные функции

Примечание. Данное описание является второй частью серии «Javascript и функциональное программирование» по изучению методов функционального программирования в JavaScript ES6+. Чтобы начать снуля, см. Часть 1

Добро пожаловать в параллельный мир

Вам необходимо кое-что узнать прежде, чем мы начнем… Если вы когда-либо программировали в JS, вы, вероятно, раньше использовали шаблоны FP! Данные шаблоны и подходы присутствовали всегда: мы просто не могли их правильно рассмотреть. Мы начнем с уже известного, а затем перейдем к исследованию новой территории. Кое-что может быть немного, скажем, странно. Но не бойтесь! Вместе мы справимся!

Полноправные функции

В Javascript функции являются объектами первого уровня. Как я уже упоминал ранее, нам не нравится загадочная терминология, поэтому давайте разъясним. Согласно глоссарию разработчиков Mozilla:

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

Мастер Йода рекомендует:  Появились первые кандидаты на вечную блокировку

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

В следующем примере мы объявим самозначимую группу символов (const) и назначим ей анонимные функции стрелок.

После первоначального присваивания constFunction является самозначимой группой символов со значением функции. Мы проверяем это путем регистрации переменной constFunction в инструменте исследования Chrome. Поскольку constFunction ‒ это функция, которую мы также можем вызвать.

Функции как значения ключей объекта

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

Функции как элементы массива

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

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

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

Асинхронная функция, которая принимаетфункцию обратного вызова

В данном примере мы используем модуль jsonfile npm для метода writeFile. Третий параметр, который ожидается writeFile, является функцией. Когда метод jsonfile.writeFile выполняет его, он либо будет успешным, либо завершится ошибкой. В противном случае он выполнит функцию errorLoggerFunction. В качестве альтернативы мы могли бы использовать более краткую синтаксическую структуру и посмотреть указанную функцию:

setTimeout

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

Начнем с чтения подписи функции. Мы можем заметить, что количество аргументов, которые принимает setTimeout, равно двум. В функциональном программировании число аргументов, которыепринимает функция, называется «арность», произошедшее от таких слов, как унарный, двоичный, тернарный и т.д. Таким образом, мы можем сказать, что setTimeout имеет арность 2 или можно сказать, что она имеет двоичную (бинарную) функцию.

Аргументами, которые ожидаются setTimeout, является функция и временной интервал ожидания перед выполнением данной функции. Хм … Другая функция, которая принимает функцию в качестве входа?

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

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

Итак. Теперь вы можете незаметно упомянуть это в любом случайном разговоре на работе / с друзьями и выглядеть, как босс! ? ������

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

В строке 5 мы объявляем массив функций. Затем мы используем метод forEach для итерации по массиву. forEach ‒ это поддерживаемая пользователем функция ES6+, которая принимает функцию, выполняемую для каждого элемента массива. Следовательно, forEach также являетсяфункцией высшего порядка!

Наш forEach принимает анонимную функцию в качестве входных данных. forEach будет перебирать массив, неявно обращаться к текущему элементу массива и называть его getCalculation. Стоит отметить, что forEach неявно обращается к элементам массива по сравнению с тем, как мы могли бы получить доступ к текущему элементу, если бы мы использовали регулярный цикл с параметром, то есть, arrayOfFunctions [i]. Каждый элемент в нашем массиве является функцией, поэтому мы вызываем getCalculation с аргументами, которые она ожидает.

Фантастика. Данный пример иллюстрирует, что функции вфункциональном программировании могут быть переданы в массивы (списки), как и любой другой тип данных. Функции могут быть переданы куда угодно!

Теперь давайте построим собственную функцию более высокого порядка!

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

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

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

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

Frontender Magazine

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

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

  • Наследование через прототипы (объекты не содержащие классов, делегирование прототипов более известное как OLOO — Objects Linking to Other Objects)
  • Функциональное программирование (с помощью лямбда-выражений и замыканий)

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

Сегодня мы создаем будущее

«Я перемещаюсь туда, где шайба должна быть, а не туда, где она уже была».

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

Кремниевая долина в 1936 году.

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

Кремниевая долина в 1971 году.

Тратили ли вы последние пару лет на изучение Angular? Если ответ «да» — я вам сочувствую. Большинство из этих знаний неприменимо к Angular 2.0 или React. И нам не угнаться за скоростью изменений, если мы будем продолжать делать то что привыкли.

Если мы собираемся выжить в качестве инженеров то, в конечном счете, мы должны адаптироваться, и адаптировать свой код.

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

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

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

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

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

Этот процесс уже коснулся таких организаций как Netflix, Facebook и Microsoft, и это случилось не просто так.

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

Почтенный мастер Ку Чи шел со своим учеником, Антоном. Надеясь вступить с мастером в дискуссию, Антон сказал: «Учитель, я слышал, что объекты это очень хорошая вещь — это правда?» Ку Чи с сожалением посмотрел на своего ученика и ответил: «Мой глупый ученик, объекты — это замыкания для бедных.» Антон поклонился и вернулся в свою келью изучать замыкания. Он внимательно прочитал всю серию работ «Lambda: The Ultimate…», потом все остальные статьи по этой теме, и создал небольшой интерпретатор с использованием объектов эмулирующих функционал замыканий. Он многому научился, и стал искать встречи с мастером, чтобы похвастаться. На своей следующей прогулке с Ку Чи, Антон сказал: «Учитель, я усердно изучал этот вопрос, и теперь понимаю, что объекты действительно являются замыканиями для бедных.» Ку Чи ударил Антона своей палкой и сказал: «Когда же ты научишься? Замыкания — это объекты для бедных». В этот самый момент Антон достиг просветления.

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

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

В первой части «Столпов» я не только описал некоторые из основных недостатков ООП, но и обозначил проблеск надежды на светлое будущее: наследование через прототипыпостроение новых экземпляров объектов из более мелких объектов, вместо классов-родителей.

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

«… шаг вперед это иногда два шага назад».

Какой-то мудрый мужик

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

Из этой работы вышли две основополагающие модели вычисления. Знаменитая машина Тьюринга, и лямбда-исчисление.

Сегодня, принципы машины Тьюринга лежат в основе всех языков программирования. Язык или набор инструкций для процессора (или виртуальной машины) называется тьюринг-полным, если на нем можно реализовать абстрактную машину Тьюринга. Первым известным примером тьюринг-полной системы было лямбда-исчисление, описанное Алонзо Чёрчем в 1936.

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

Lisp был (и остается) чрезвычайно популярным языком в научном мире, кроме этого его использовали в системах с непрерывным потоком данных, таких как масштабируемые системы обработки векторной графики. Изначальная спецификация Lisp была разработана в 1958, и этот язык до сих пор встроен в большое количество сложных приложений. Примечательно, что почти все популярные САПР поддерживают AutoLISP, диалект Lisp, используемый в AutoCAD, который, в свою очередь, вместе с многочисленными клонами, до сих пор является самой широко используемой САПР в мире.

Непрерывные данные: данные которые могут принимать любое значение на непрерывной шкале и их легче измерить, чем подсчитать (примеры — температура и давление). В противоположность дискретным данным, которые могут быть подсчитаны и легко проиндексированы. Все значения приходят из известного набора допустимых значений. В частности, звучание скрипки может быть представлена непрерывным набором данных, потому что смычок может находиться в любом месте грифа в любой момент времени. В противоположность, звуки, полученные с помощью пианино, наоборот являются дискретными, так как каждая клавиша настраивается на определенное звучание, и невозможно извлечь звуки отличные от тех что уже настроены. Обычно у пианино используется 88 клавиш. Количество звуков же, которые может производить скрипка, подсчитать невозможно, потому что скрипач производит их с помощью скольжения смычком вверх-вниз между разными нотами.

Lisp и его производные породили целое семейство функциональных языков программирования, таких как: Curry, Haskell, Erlang, Clojure, ML, OCaml и т.д.

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

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

JavaScript позволяет легко присваивать функции переменным, передавать их в другие функции, возвращать функции из функций, выполнять композицию функций, и т.д. Этот язык также предлагает иммутабельные структуры данных (прим. пер.: Object.freeze() для объектов, const для примитивных типов) и функции, которые позволяют легко возвращать новые объекты и массивы вместо изменения тех, которые передаются в качестве аргументов.

Почему это так важно?

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

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

Идемпотентность: При одинаковых входных параметрах, чистая функция всегда вернет тот же самый результат, независимо от того сколько раз она была вызвана. Возможно, вы слышали это слово при описании HTTP GET запросов. Идемпотентность важна при создании RESTful-сервисов в вебе, но она также полезна в программах которые зависят от времени и порядка операций — без нее точно не обойтись при параллельных вычислениях и горизонтальном масштабировании.

Из-за этого свойства, чистые функции отлично подходят когда необходимо работать с непрерывными наборами данных. Например, постепенное «проявление/затухание» сигнала в видео или аудио. Как правило, подобные алгоритмы выполняются в виде линейных или логарифмических кривых вне зависимости от частоты кадров или частоты дискретизации, и используются в последней точке непрерывных данных(аудио/видеосигнал) для получения дискретных значений. В графике, аналогичные функции применяются для масштабирования векторных изображений, таких как SVG, шрифты и файлы Adobe Illustrator. Ключевые кадры, или контрольные точки, располагаются по отношению друг к другу относительно или находятся в разных местах по времени, а не фиксированно с известным положением пикселей, кадров и прочих единиц измерения.

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

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

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

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

Чистые функции это ответ ФП на ООП-проблему гориллы и банана:

«Проблема объектно-ориентированных языков в том что они тащут за собой всю неявную среду. Вы хотите получить банан, но кроме банана в нагрузку получаете гориллу, и все чертовы джунгли!».

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

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

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

Базовые методы работы со списками:
  • head() — получить первый элемент,
  • tail() — получить все элементы кроме первого,
  • last() — получить последний элемент,
  • length() — вернуть количество элементов.
Утверждения / сравнения:
  • equal() ,
  • greaterThan() ,
  • lessThan() .
Поиск по спискам:
  • find() ([х]) -> y — взять список х и вернуть первый элемент, соответствующий условию,
  • filter () ([х]) -> [у] — взять список х и вернуть все элементы, соответствующие условию.
Преобразование списков:
  • map() ([х]) -> [у] — взять список х и применить преобразование к каждому элементу в этом списке, вернуть новый список y ,
  • reverse() ([1, 2, 3]) -> [3, 2, 1] ,
  • reduce() ([х], function [, accumulator]) -> y — применить функцию к каждому элементу списка x и объединить результат в одно значение,
  • any() — истина , если любое из значений соответствует условию,
  • all() — истина , если все значения соответствуют условию,
  • sum() — сумма всех значений,

  • product() — произведение всех значений,
  • maximum() — максимальное значение,
  • minimum() — минимальное значение,
  • concat() — вернуть список в котором соединены все переданные списки.
Итераторы / Генераторы / Коллекторы (бесконечные списки):
  • sample() — возвращает текущее значение непрерывного источника (температура, формы ввода текста, переключатель, и т.д),
  • repeat() — (1) -> [1, 1, 1, 1, 1, 1, …] ,
  • cycle() / loop() — вернуться и начать все заново, когда будет достигнут конец списка.

Некоторые из этих функций были добавлены в JavaScript при принятии стандарта ECMAScript5, например дополнительные методы массивов:

Списки в роли общего интерфейса

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

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

Процесс внедрения в функцию поддержи работы с разными типами данных называется «поднятие» (lifting).

Он основан на том, что все эти данные обрабатываются списком, то есть список как тип данных является единым интерфейсом взаимодействия. Я обсуждал лифтинг в лекции «Static Types are Overrated» на Fluent 2014.

Как прекратить обращать внимание на мелочи

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

Реактивное программирование использует функциональные методы map , filter и reduce для создания и обработки потоков данных,распространяемых внутри системы. Когда ввод X изменяется, вывод Y автоматически обновляется в ответ, именно поэтому мы и используем термин «реактивное», то есть основанное на реакции.

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

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

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

Поток данных это список значений, распределенный на временной шкале.

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

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

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

Пример того как это может выглядеть:

Еще больше асинхронности

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

Ну, на самом деле это работает не совсем так, потому что .then() будет вызвано только когда цены на акции станут доступны, поэтому когда becomeAMillionaire наконец-то выполнится — цены на «будущие» акции уже станут настоящими.

В общем случае, промис это поток который сообщает только одно значение (или ошибку). Наблюдаемые значения могут заменить промисы, плюс обеспечить весь функционал стандартных утилит, к которым вы привыкли, если используете библиотеки наподобие Underscore или Lo-Dash.

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

Приближаясь к 2015 году (прим. пер.: статья писалась в 2014 году), мы возвращаемся к корням информатики заложенным в 1930-х — чтобы легче управлять программным обеспечением будущего.

Информация для дальнейшего ознакомления

Мне очень понравилась лекция технического руководителя Netflix Джафара Хусайна на конференции Scale @2014. Она называется «Asynchronous Programming at Netflix».

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

Если вы готовы заняться реактивным программированием, ознакомьтесь с Reactive Extensions for JavaScript, The Reactive Manifesto, и поработайте с этими интерактивными уроками.

Для понимания функциональных потоков в node.js ознакомьтесь со статьей «How I Want to Write Node: Stream All the Things!» и библиотекой Highland.js.

Для детального понимания концепций реактивности, ознакомьтесь с отличной работой «General Theory of Reactivity» Криса Ковальски.

Справочник современных концепций JavaScript: часть 1

Основы функционального программирования, реактивного программирования и функционального реактивного программирования на JavaScript

Кратко: В первой части серии «Справочник современных концепций JavaScript» мы познакомимся с функциональным программированием (FP), реактивным программированием (RP) и функциональным реактивным программированием (FRP). Для этого мы узнаем о чистоте, состоянии и его отсутствии, неизменяемости и изменяемости, императивном и декларативном программировании, функциях высшего порядка, observables и парадигмах FP, RP, FRP.

Введение

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

Концепции

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

  • Чистота: чистые функции, нечистые функции, побочные эффекты
  • Состояние: с ним и без него
  • Неизменяемость и изменяемость
  • Императивное и декларативное программирование
  • Функции высшего порядка
  • Функциональное программирование
  • Observables: горячие и холодные
  • Реактивное программирование
  • Функциональное реактивное программирование

Чистота

Чистые функции

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

Функция half(x) принимает число x и возвращает значение половины x . Если мы передадим этой функции аргумент 8 , она вернет 4 . После вызова чистая функция всегда может быть заменена результатом своей работы. Например, мы могли бы заменить half(8) на 4 : где бы эта функция не использовалась в нашем коде, подмена никак не повлияла бы на конечный результат. Это называется ссылочной прозрачностью.

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

  • Чистые функции должны принимать аргументы.
  • Одни и те же входные данные (аргументы) всегда произведут одинаковые выходные данные (вернут одинаковый результат).
  • Чистые функции основываются только на внутреннем состоянии и не изменяют внешнее ( примечание: console.log изменяет глобальное состояние).
  • Чистые функции не производят побочных эффектов.
  • Чистые функции не могут вызывать нечистые функции.

Нечистые функции

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

Рассмотрим следующие примеры:

Побочные эффекты в JavaScript

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

Подводя итог: чистота

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

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

Состояние

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

С состоянием

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

Без состояния

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

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

Подводя итог: состояние

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

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

Неизменяемость и изменяемость

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

Неизменяемый

Если объект является неизменяемым, его значение не может быть изменено после создания.

Изменяемый

Если объект изменяем, его значение может быть изменено после создания.

Реализация: неизменяемость и изменяемость в JavaScript

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

Используя метод .substring() на нашем Hello! , строка не изменяет исходную строку. Вместо этого она создает новую строку. Мы могли бы переопределить значение переменной str на что-то другое, но, как только мы создали нашу строку Hello! , она навсегда останется Hello! .

Числовые литералы также неизменяемы. Следующий пример всегда будет иметь одинаковый результат:

Ни при каких обстоятельствах 1 + 2 не может стать чем-либо, кроме 3 .

Это демонстрирует, что в JavaScript присутствует реализация неизменяемости. Однако разработчики JS знают, что язык позволяет изменить многое. Например, объекты и массивы изменяемы. Рассмотрим следующий пример:

В этих примерах исходные объекты изменены. Новые объекты не возвращаются.

Чтобы узнать больше об изменяемости в других языках, ознакомьтесь с Изменяемые и неизменяемые объекты.

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

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

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

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

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

Подводя итог: неизменяемость и изменяемость

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

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

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

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

Императивное и декларативное программирование

Хотя некоторые языки были разработаны как императивные (C, PHP) или декларативные (SQL, HTML), JavaScript и другие (такие как Java и C# могут поддерживать обе парадигмы программирования.

Мастер Йода рекомендует:  Рядовой или генерал кто ты в армии программистов

Большинство разработчиков, знакомых с даже самым простым JavaScript, писали императивный код: инструкции, информирующие компьютер как достичь желаемого результата. Если вы использовали цикл for , вы писали императивный JS.

Декларативный код сообщает компьютеру, что вы хотите достичь, а не как, и компьютер сам заботится о том, как достичь конечного результата без явного описания этого разработчиком. Если вы использовали Array.map , вы писали декларативный JS.

Императивное программирование

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

Рассмотрим функцию, увеличивающую каждое число в массиве целых чисел. Императивным примером в JavaScript может быть:

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

Декларативное программирование

Декларативное программирование описывает, что выполняет логика программы без описания того, как это сделать.

Очень простой пример декларативного программирования может быть продемонстрирован с помощью SQL. Мы можем запросить таблицу базы данных ( People ) для людей с фамилией Smith следующим образом:

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

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

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

Примечание: в JavaScript map, reduce и filter — декларативные, функциональные методы массивов. Библиотеки утилит, такие как lodash, предоставляют такие методы takeWhile, uniq, zip и другие в дополнение к map , reduce и filter .

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

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

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

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

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

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

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

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

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

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

Функции высшего порядка также могут возвращать другую функцию:

Подводя итог: функции высшего порядка

Природа JavaScript функций как объектов первого класса делает их основой функционального программирования в JS.

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

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

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

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

Функциональное программирование охватывает приведенные выше концепции следующими способами:

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

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

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

Код доступен для запуска здесь — JSF >processCopy() — нечистый контейнер, выполняющий ядро ​​и управляющий побочными эффектами. Мы использовали функцию высшего порядка, принимающую другие функции в качестве аргументов для поддержания функционального стиля.

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

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

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

Observables

Observables похожи на массивы, за исключением того, что элементы не хранятся в памяти, а поступают асинхронно с течением времени (потоки). Мы можем подписаться на observables и реагировать на их события. JavaScript оbservables — это реализация шаблона observer. Реактивные расширения (обычно называемые с префиксом Rx) предоставляют observables для JS через RxJS.

Для демонстрации концепции observables давайте рассмотрим простой пример: изменение размера окна браузера. В этом контексте observables максимально понятны. Изменение размера окна браузера испускает поток событий в течение определенного периода времени (пока окно принимает нужный размер). Мы можем создать observable и подписаться на него, чтобы реагировать на поток событий изменения размера:

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

Горячие observable

Пользовательские интерфейсные события (нажатие кнопки, перемещения мыши и т.д.) — горячие. Горячие observable всегда будут срабатывать, даже если мы специально не реагируем на них подпиской. Пример изменения размера окна выше — горячий observable: resize$ observable отрабатывает вне зависимости от того, существует подписка или нет.

Холодные observable

Холодные observable срабатывают только тогда, когда мы подписываемся на него. Если мы подпишимся снова, он начнет все сначала.

Давайте создадим observable массив чисел от 1 до 5 :

Мы можем подписаться на только что созданный source$ observable. После подписки, значения последовательно посылаются к observer. Обратный вызов onNext логирует значения: Next: 1 , Next: 2 , и т.д. до завершения: Completed! . Созданный нами холодный observable source$ не отрабатывает до того, пока мы не подпишемся на него.

Подводя итог: observables

Observables являются потоками. Мы можем наблюдать любой поток: от событий изменения размеров в существующих массивах до ответов API. Мы можем создать observables практически из всего, что угодно. Promise — тот же observable, но отдающий только одно значение, а observables могут возвращать много значений с течением времени.

Мы можем работать с observables по-разному. RxJS использует множество операторов. Observables часто визуализируется с помощью точек на линии, как показано на сайте RxMarbles. Поскольку поток состоит из асинхронных событий с течением времени, легко осмыслять это линейным способом и использовать именно такие зрительные образы, чтобы понять реактивные операторы. Например, следующие изображение от RxMarbles иллюстрирует оператор фильтра:

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

Реактивное программирование

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

Реактивное программирование часто связано с Reactive Extensions, API для асинхронного программирования с observable потоками. Реактивные расширения (с префиксом Rx) предоставляют библиотеки для различных языков, включая JavaScript (RxJS).

На практике: реактивное программирование на JavaScript

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

Мы будем использовать RxJS и для реализации нашей функциональности создадим поток входных событий, вот так:

Этот код можно запустить на JSF >confCodeInput . Затем мы используем оператор map для получения значения из каждого входного события. Далее мы фильтруем любые результаты, не являющиеся шестью символами, чтобы они не появлялись в возвращенном потоке. Наконец, мы подписываемся на наш confCodes$ observable и выводим подошедший вариант. Обратите внимание, что это произошло в ответ на событие, декларативно, — это суть реактивного программирования.

Подводя итог: реактивное программирование

Парадигма реактивного программирования включает наблюдение и реагирование на события в асинхронных потоках данных. RxJS используется в Angular и набирает популярность как решение JavaScript для реактивного программирования.

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

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

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

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

Более полное определение от родоначальника FRP Конала Эллиота звучит так: функциональное реактивное программирование — денотативное и продолжительное во времени. Эллиот предпочитает описывать эту парадигму программирования как денотативное программирование с продолжительностью во времени, а не «функциональное реактивное программирование».

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

  • денотатив: значение каждой функции или типа является точным, простым и независимым от реализации (функциональная часть)
  • продолжительно во времени: переменные имеют конкретное значение короткий промежуток времени: между любыми двумя точками есть бесконечное число других точек; обеспечивает гибкость трансформации, эффективность, модульность и точность (реактивная часть)

Чтобы понять продолжительность во времени, рассмотрим аналогию с использованием векторной графики. Векторная графика имеет бесконечное разрешение. В отличие от растровой графики (дискретное разрешение) векторная графика масштабируется безгранично, она никогда не перерисовывается или становится нечеткой. «Выражения FRP описывают целые эволюции значений во времени, представляя эти эволюции непосредственно как значения первого класса», — Конал Эллиот.

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

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

Здесь можно рассмотреть слайды Колала Эллиота о cущности и происхождении FRP. Язык программирования Haskell поддается истинному FRP благодаря своей функциональной, чистой и ленивой природе. Эван Кзаплиски, создатель Elm, дает большой обзор FRP в своем выступлении «Управление временем и пространством: понимание многих подходов FRP».

В самом деле, давайте коротко поговорим об Elm Эвана Кзаплиски. Elm — это функциональный, типизированный язык для создания веб-приложений. Он компилируется в JavaScript, CSS и HTML. Архитектура Elm послужила вдохновением для контейнера состояния Redux приложений JS. Первоначально Elm позиционировался истинным функционально реактивным языком программирования, но начиная с версии 0.17 он реализовывал подписки вместо сигналов в интересах облегчения изучения и использования языка. На этом Elm простился с FRP.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

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

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

Читайте нас на Медиуме , контрибьютьте на Гитхабе , общайтесь в группе Телеграма , следите в Твиттере и канале Телеграма , скоро подъедет подкаст. Не теряйтесь.

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

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

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

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

Императивный JavaScript

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

Хотя разработчики изо всех сил старались сопоставить гибкость JavaScript со сложностью объектной модели документа браузера(DOM), фактический код JavaScript часто выглядел чем-то вроде:

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

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

Объектно-ориентированный JavaScript

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

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

В этой объектно-ориентированной версии, функция конструктора имитирует класс для моделирования объекта, который нам нужен. Методы включены в прототип нового объекта, для того, чтобы сохранить минимальное использование памяти. И весь код изолирован в анонимной функции-выражении, вызываемой сразу после создания, чтобы не засорять глобальную область действия идентификатора. Там даже есть директива строгого использования, для того, чтобы можно было воспользоваться последним движком JavaScript, а старомодный метод OnClick заменен новым addEventListener, потому что, кто сегодня в здравом уме использует версию IE8 или более ранние версии? Скрипт, подобный этому, вероятно будет вставлен в конце элемента в HTML-документе, чтобы убедиться, что вся DOM загружена, прежде чем указанный скрипт будет обработан, так что , к которой он относится, будет доступна.

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

Функциональный JavaScript

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

Вы заметили, насколько короче данная версия? Мы задали только две функции: capify и processWords. Каждая из этих функций чистая, это значит, что они , это означает, что они не зависят от своего положения в коде, из которого они вызываются. . Функции не создают побочные эффекты, которые изменяют переменные. Существует один и только один результат, возврата функции для любого набора аргументов. Из-за проведенных улучшений, новые функции очень легко протестировать, они также могут быть легко вырезаны из кода, и использованы в любом другом месте без каких-либо дополнительных модификаций.

Возможно здесь есть только одно ключевое слово, которое вы могли не знать, если до этого не сталкивались с функциональным кодом. Мы воспользовались новым методом map в Array, для того чтобы применить функцию к каждому элементу временного массива, который мы создали, когда разделили нашу строку. Map является лишь одним из немногих удобных методов, появившихся в следствие того, что современные браузеры и серверный интерпретатор JavaScript внедрили стандарты ECMAscript 5. Примените map, на месте цикла for, убрав переменную counter, и ваш код станет намного чище и читабельнее.

Начните мыслить функционально

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

  • Зависят ли мои функции от контекста, в котором они вызываются, или они чистые и независимые?
  • Могу ли я написать эти функции таким образом, чтобы они всегда возвращали один и тот же результат для заданных входных данных?
  • Уверен ли я, что мои функции не изменяют ничего лишнего?
  • Если бы я хотел использовать эти функции в другой программе, мне нужно было бы внести изменения в них?

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

Я экспериментирую с функциональным программированием, используя массив объектов.

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

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

Есть ли лучший способ сделать это?

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

В функциональном программировании функции чистые, что означает:

  • Они не вызывают побочных эффектов (они не изменяют переменные за пределами их области действия).
  • Учитывая тот же ввод, они всегда производят один и тот же вывод

Функции, которые вы определяете, не являются чистыми, потому что они

  • изменять переменные за пределами их области
  • возвращает разные результаты, в зависимости от состояния переменных вне их области

Итак, эта функция нечиста:

в то время как этот чистый:

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

Все ли это чисто?

Одна функция в этом коде еще не чиста! updateHTML принимает два аргумента и всегда возвращает undefined . Но по пути это вызывает побочный эффект: он обновляет DOM!

Если вы хотите обойти эту проблему обновления DOM вручную на основе нечистых функций, я бы рекомендовал посмотреть на библиотеки/фреймворки, такие как React, Elm, Cycle.js или Vue.js — все они используют концепцию под названием виртуальный DOM, который позволяет представлять всю DOM как структуру данных JS и позаботиться о синхронизации виртуальной DOM с реальной для вас.

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

Функциональные языки (например, Haskell, Lisp, Clojure, Elm) заставляют вас писать чистые функции и внедрять множество концепций сгибания ума, когда ваш фон больше связан с процедурным или объектно-ориентированным программированием. На мой взгляд, JavaScript — отличный язык, чтобы «наткнуться» на функциональное программирование. Хотя на первый взгляд это похоже на Java, JS имеет гораздо больше общего с Lisp, когда вы смотрите ближе. Пытаясь понять такие вещи, как закрытие и прототипное наследование вместо того, чтобы пытаться писать JS, как будто это была Java, мне очень помогло. (Отлично читается в этом контексте: Два столпа JavaScript)

Для следующих шагов в функциональном JavaScript я бы рекомендовал

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

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

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

Итак, я получил академию, но я полагал, что для этого должно быть что-то более фундаментальное. Я боролся, и однажды прочитал строку (я забыл где) «вместо мелкозернистого, думаю, что зерно».

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

Я использовал это успешно в качестве примера раньше: класс с целым рядом итераций в нем, вы видите их все время в ООП (это наивный пример, но вы получаете идею):

Итак, теперь я могу сделать что-то вроде этого:

Функциональное право? Похоже на ваш опубликованный код. Хм. не совсем. Это своего рода декларативное, но не более функциональное использование более высокого порядка или первого класса. Как мы можем сделать шаг в этом направлении?

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

Итак, теперь я могу сделать это (опять же, абсолютно наивно, вам нужно убедиться, что вы получаете массив чисел, проверяете нуль, когда это необходимо):

Не нирвана функционального, а шаг в правильном направлении. Также подумайте, как это изменит то, что вы проверите. И я мог бы просто передать функции prod и sum как функции anon прямо в args reduceArray, если бы захотел, поэтому мне даже не пришлось бы объявлять их:

И, конечно, функциональные парни, похоже, все это в одном утверждении:

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

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

Это изменяет то, как вы думаете обо всем: например, на итерации вы можете услышать, как парень ООП настаивает на том, что инкрементный var должен иметь «значащее» имя. Что-то вроде «for (name in ArrayNames) <. >);

Но если вы итерации «курсовое зерно», нет значимого имени, кроме «элемента». Таким образом, вы можете просто использовать «val» или даже просто «d» или «v» (которые вы все время видите в функциональных примерах). Конкретный тип, который выполняется итерацией, не является критическим, если он может быть повторен и до тех пор, пока вы проверили свою инструкцию.

(OMG, что я сказал. конкретный тип на самом деле не имеет значения? HE WITCH BURN HIM. ).

Опять же, совершенно наивный пример, но тот, который я использовал с успехом. Как только люди заглядывают в него, они говорят такие вещи, как «о. но вы могли бы взять те учебные функции, которые вы определили, и добавить их в модуль util, чтобы вы могли создать reduceArray.prod(myArray), делая более высокий порядок использовать более надежные. «. Внезапно это не все о классах и интерфейсах.

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

Опять не функциональная нирвана, но прыжок в правильном направлении.

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