Найти тему
Мир в it

Functional Reactive Progammging. Что это такое?

Оглавление

Programming

Что есть программирование? Программирование - это "сидеть и тыкать в свои кнопочки, пока нормальные люди по-настоящему работают" Так думают все, кроме программистов Но это же правда По факту мы просто сидим и жмем кнопки в определенном порядке Другое дело, что этот порядок еще придумать надо

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

Какое бывает программирование?

Императивное Мы говорим компьютеру как он должен решать определенную задачу

Пример: Пусть у нас есть задача Вывести сумму всех чисел, от 1 до 1000, делящиеся на 3 или 5

Python:

s = 0
x = 1
while x < 1000:
if x % 3 == 0 or x % 5 == 0:
s += x
x += 1
print(s)

Декларативное Мы говорим компьютеру какую задачу он должен решать

Пример: Та же задача

Python:

divides = lambda x, y: x % y == 0
correct = lambda x: divides(x, 3) or divides(x, 5)
print(sum(filter(correct, range(1, 1000))))

Нам нужно меньше тыкать в свои кнопочки? Сомнительно Нам проще придумывать то, что мы должны написать? Несомненно

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

Отлично, работаем дальше

Reactive

Что такое реактивность? Все, кому не лень, при объяснении FRP приводят в пример MS Excel Мне лень приводить в пример MS Excel... Приведу в пример Google Sheets У нас есть много ячеек, в которые мы можем записать какие-то значения или какие-то формулы, зависящие от других ячеек или констант И когда мы меняем ячейку А, от которой зависит ячейка Б, у нас меняется как ячейка А, так и пересчитывается значение ячейки Б

И чем же это хорошо? Чего далеко ходить Допустим у нас есть два инпута - А и Б, в которые предполагается вписывание некоторых чисел И рядом мы почему-то хотим писать значение формулы (5*А+Б)

Как мы это обычно делаем У нас есть функция, которая ищет А, берет из него значение, ищет Б, берет из него значение, вычисляет значение формулы (5*А+Б), ищет текстовое поле, в которое нужно писать результат, перезаписывает значение этого поля Затем мы эту функцию весим как eventListener на события keypressed у А и Б И оно работает кое-как

В реактивном подходе мы говорим компьютеру: "Вот ячейка А, вот ячейка Б, вот ячейка, в которую пиши значение формулы (5*А+Б)" (довольно декларативно) И когда мы будем изменять значение А или Б, автоматически будет меняться текст рядом с инпутами И никаких событий мы, как программисты, отслеживать не должны

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

Вторая часть FRP - это Streams

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

Например рассмотрим клики мышки И будем представлять это как стрим После того, как произошел клик, в последовательности появился новый элемент

Существует такое высказывание - "Всё, или почти всё может быть стримом"

Мы можем представить работу всего, или почти всего как работу с последовательностями

Чтобы понять, насколько важны последовательности в FRP, приведу пример описания одной из функций FRP библиотеки: "Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence."

Со стримами можно производить некоторые операции Эти операции очень удобно представлять с помощью диаграм А эти диаграммы красиво и понятно выглядят в ASCII графике

--a---b-c---d---X---|->

a, b, c, d - полученные значения
X - ошибка
| - сигнал "завершено"
---> - течение времени

Приведу несколько примеров:

Map:

a: -------------1----2--->
a.map(*2): -----2----4--->

Filter:

a: -------------1----2--->
a.filter(%2): -------2--->

Merge:

a: -----------1---3---->
b: --------------2------>
a.merge(b): --1-2-3---->

Combine:

a: ---------------1-----3--4->
b: -------------------2------>
a.combine(b, +): -----3-5--7->

Scan:

a:--------------------------"c"---"a"---"t"---->
a.scan("", ".concat"): -""--"c"---"ca"--"cat"-->

Почему удобны стримы?

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

Как будет выглядеть код, работающий с событиями? Как-то так:

var imgLinks = ['#1', '#2', '#3'];
var curImg = 1;

$(document).keydown(function(e) {
if (e.keyCode !== 37 && e.keyCode !== 39) {
return;
}

var imgContainer = $('#img');

switch(e.keyCode) {
case 37:
curImg--;
break;
case 39:
curImg++;
break;
}

imgContainer.attr('src', imgLinks[curImg]);
});

Как будет выглядеть код, работающий со стримами и свойствами? Как-то так:

// Definitions

var toKeyCode = (e) => e.keyCode;

var leftArrow = (keyCode) => keyCode === 37;
var rightArrow = (keyCode) => keyCode === 39;

var leftSide = (_) => -1;
var rightSide = (_) => 1;

var nextIndex = (curIdx, side) => curIdx + side;

var getImg = (images, idx) => images[idx];

// Process

var keyCodesStream = $(document).asEventStream('keydown').map(toKeyCode);

var leftSideStream = keyCodesStream.filter(leftArrow).map(leftSide);
var rightSideStream = keyCodesStream.filter(rightArrow).map(rightSide);
var sideStream = leftSideStream.merge(rightSideStream);

var images = Bacon.constant(['#1', '#2', '#3']);
var imgIndex = sideStream.scan(0, nextIndex).toProperty();
var imgLink = Bacon.combineWith(getImg, images, imgIndex);

imgLink.assign($('#img'), 'attr', 'value');

"УУУУУ....нафиг это наааадо....смотрите сколько писаааать" Но заметьте, здесь совсем нет ничего сложного Возможно сначала это кажется непонятным, но это до того, как начать читать

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

Разумеется, внутри того, что я показал, лежат события Но они спрятаны, мы не должны работать непосредственно с ними У нас лишь есть последовательность каких-то элементов И мы должны лишь говорить компьютеру, ЧТО он должен с этими элементами делать

Смотря на мой пример, задаешься вопросом: "а откуда эти функции то вообще взялись" ...все же задались этим вопросом?

Ну ладно, раскрою тайну Всё дело в библиотеках!

Существует приличное количество FRP библиотек Точного числа я не назову, но оно больше трёх Как минимум про четыре я слышал, а про три из них я сейчас расскажу вам

Итак, первая библиотека, которую я покажу, называется ReactiveX Её разрабатывает Microsoft Все шутят - "Удивительно, что Microsoft сделала что-то хорошее" Не понимаю этой шутки, у них же вон IE8 есть, например

Но ReactiveX действительно очень мощная и популярная библиотека Она адаптирована для JavaScript: RxJS, для Java: RxJava Ну и еще для нескольких языков: C#: Rx.NET C#(Unity): UniRx Scala: RxScala Clojure: RxClojure C++: RxCpp Lua: RxLua Ruby: Rx.rb Python: RxPY Groovy: RxGroovy JRuby: RxJRuby Kotlin: RxKotlin Swift: RxSwift PHP: RxPHP

Еще есть библиотека BaconJS Собственно с помощью нее и написан мой пример Беря утверждения с потолка, эта библиотека самая популярная на сегодня

Для сравнения этих двух библиотек, скажу, что RxJS побеждает BaconJS в скорости А BaconJS выигрывает RxJS в размере библиотеки - 43.5КБ против 137КБ

Третья библиотека, про которую я скажу, называется KefirJS И, как можно понять по названию, это всё наше, родное Она, к слову, весит 10КБ И она очень быстрая - быстрее RxJS Цитата с их сайта: "Kefir — is a Reactive Programming library for JavaScript inspired by Bacon.js and RxJS, with focus on high performance and low memory usage."

Итак, хватит говорить про реактивность, как будто это самая важная часть FRP Вообще говоря, самая важная часть FRP, наверное, Programming Ну да ладно Идем к функциональности

Functional

Мир функциональщины довольно-таки огромен. Я расскажу лишь про два из принципов, которым следует функциональное программирование

Immutability

Pure Functions