Kefir.js - нова бібліотека для функціонального реактивного програмування (FRP) в JavaScript

Напевно багато хто вже чули про підхід FRP для організації асинхронного коду. На хабре вже писали про FRP (Реактивне програмування Haskell, FRP на Bacon.js) і є хороші доповіді на цю тему (Программировние UI з допомогою FRP і Bacon.js, Functional Reactive Programming & ClojureScript, Про Bacon.js Juha від Paananen — автора бекону

Якщо коротко, FRP це підхід схожий на Promise, але з необмеженою кількістю значень, і великою кількістю методів для комбінування / модифікування потоків подій. Іншими словами, якщо Promise дозволяють працювати зі значенням, якого у вас ще немає, так, ніби вона у вас вже є, то FRP дозволяє працювати зі значенням, що змінюється в часі, так, ніби воно не змінюється.

Ось що це дає порівняно із зворотніми викликами:

1) Потік подій (Event stream) і значення змінюється у часі (Property або Behavior) стають об'єктами першого класу. Це означає, що їх можна передавати в функції і повертати з функцій.

Наприклад, можна створити об'єкт містить кліки на кнопку (потік подій), і далі робити з цим об'єктом все, що можна робити з звичайної змінної — передавати у функцію, повертати з функції, зберігати як властивість іншого обєкта і т.д. Чи можна створити об'єкт відображає поточний розмір вікна браузера (значення змінюється в часі).

Це дозволяє набагато краще розділяти відповідальності в коді, розділяти його на модулі, і писати більш гнучкий, короткий і керований код.

До прикладу можна написати функцію, яка повертає потік перетаскиваний (drag). В якості параметрів вона буде приймати 3 потоку — початок перетягування, рух, кінець перетягування. Далі можна передати цю функцію: або потоки для відповідних подій миші (mousedown, mousemove, mouseup), або для touch подій (touchstart, touchmove, touchend). Сама ж функція не буде нічого знати про джерела подій, а буде працювати лише з абстрактними потоками. Приклад реалізації на Bacon.

2) Явний state

Друге велике перевага FRP це явне управління станом. Як відомо, state — один з найголовніших джерел складності програм, тому грамотне управління ним дозволяє писати більш надійні і прості в підтримці програми. Відмінний доповідь від Річа Хіккі про складності (complexity) «Simple Made Easy».

FRP дозволяє писати велику частину коду на «чистих функції» і управляти потоком даних явно (з допомогою потоків подій), а стану зберігати теж явно в Property.



Kefir.js



Зараз є дві основні FRP бібліотеки для JavaScript, це Bacon.js і RxJS. Як мені здається, Bacon більш близький духом функціонального програмування, а RxJS це щось зі світу ООП. У Rx дуже важка для сприйняття документація — її по-перше багато, а по-друге вона написана в дуже формальному стилі (як автогенерируемая документація з вихідного коду). Тобто Rx важче вивчати і важче їм користуватися. Але Rx більш швидкий і споживає менше пам'яті.

Остання обставина іноді буває ахіллесовою п'ятою Bacon. Вперше я помітив проблему, коли спробував написати аналог scrollMonitor на Bacon. Вийшов дуже хороший API з усією міццю FRP, але коли я запустив цей стрес тест, все просто зависло. Як виявилося, Bacon споживає купу пам'яті і часті сміття викликаю фризи. Це може бути актуально при великій кількості потоків або на мобільних пристроях. Я вважаю що в бібліотеці повинен бути більший запас продуктивності, щоб менше думати про неї при написанні коду програми!

Kefir.js — нова FRP билиотека, над якою я працюю останні кілька місяців. API Kefir дуже схожий на API Bacon, але в Kefir я приділяю багато уваги продуктивності і споживання пам'яті. Зараз Kefir приблизно в 5-10 разів швидше Bacon, і в 1-2 рази швидше Rx, приблизно теж і з пам'яттю.

Порівняння продуктивності Kefir і Bacon в живій тесті. Також є результати синтетичних тестів пам'яті. Ще є синтетичні тести продуктивності, ось результати деяких з них:

stream.map(id)
----------------------------------------------------------------
Kefir x 7,692,055 ops/sec ±1.62% (33 runs sampled)
Bacon x 703,734 ops/sec ±1.63% (34 runs sampled)
RxJS x 2,303,480 ops/sec ±1.70% (34 runs sampled)
-----------------------
Kefir 1.00 Bacon 0.09 RxJS 0.30


stream.map(id) with multiple listeners
----------------------------------------------------------------
Kefir x 4,185,280 ops/sec ±0.89% (34 runs sampled)
Bacon x 421,695 ops/sec ±0.79% (33 runs sampled)
RxJS x 604,156 ops/sec ±1.21% (31 runs sampled)
-----------------------
Kefir 1.00 Bacon 0.10 RxJS 0.14


stream.flatMap (x) -> Lib.once(x)
----------------------------------------------------------------
Kefir x 1,073,871 ops/sec ±1.14% (32 runs sampled)
Bacon x 57,474 ops/sec ±4.45% (28 runs sampled)
-----------------------
Kefir 1.00 Bacon 0.05


stream.combine(Lib.constant(1), fn)
----------------------------------------------------------------
Kefir x 2,413,356 ops/sec ±1.14% (34 runs sampled)
Bacon x 220,898 ops/sec ±1.41% (34 runs sampled)
-----------------------
Kefir 1.00 Bacon 0.09


stream.skipDuplicates()
----------------------------------------------------------------
Kefir x 7,009,320 ops/sec ±1.49% (33 runs sampled)
Bacon x 684,319 ops/sec ±1.55% (34 runs sampled)
RxJS x 401,798 ops/sec ±1.48% (31 runs sampled)
-----------------------
Kefir 1.00 Bacon 0.10 RxJS 0.06


Також я намагаюся зробити Kefir максимально простим для вивчення, приблизно як Underscore або LoDash. Тому і документація дуже схожа на документацію Underscore. Мета — зробити документацію краще ніж і Rx і в Bacon.

Ще одна мета Kefir — це переосмислення API Bacon. Bacon довго розвивався і, за необхідності підтримки зворотної сумісності, API а деяких місцях став трохи кострубатим. У Kefir є можливість написати все з чистого аркуша, і я намагаюся цією можливістю користуватися.

Поточний стан


Зараз Kefir знаходиться у стані розробки, але вже багато всього є, і ним можна користуватися. Документація теж ще не повна, але я сподіваюся скоро її дописати і підтримувати в повному стані при додаванні нових фіч.

Порівняно з Bacon зараз в Kefir не вистачає:

  • Помилок як подій, є лише значення
  • Частині методів / комбінаторів: zip, combineTemplate, when, update, різних методів для буферів, та деяких інших
  • Атомарних подій (Rx, до речі, теж немає)


Ось і все що я хотів поки розповісти про Kefir. Я не описував детально саму бібліотеку, тому Kefir дуже схожий на Bacon, і якщо ви знайомі з останнім, то без праці освоїте перший. А якщо ні, то можна вивчати Kefir за туториалам Bacon, поглядаючи в документацію кефіру :-)

github.com/pozadi/kefir — проект на GitHub
pozadi.github.io/kefir — документація

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.