📜 ⬆️ ⬇️

Kefir.js - a new library for functional reactive programming (FRP) in JavaScript

Surely, many have already heard about the FRP approach for organizing asynchronous code. On Habré already wrote about FRP ( Reactive programming in Haskell , FRP on Bacon.js ) and there are good reports on this topic ( Programming UI using FRP and Bacon.js , Functional Reactive Programming & ClojureScript , About Bacon.js from Juha Paananen - Bacon's author )

In short, FRP is an approach similar to Promise, but with an unlimited number of return values, and more methods for combining / modifying event streams. In other words, if the Promise allows you to work with a value that you do not already have, as if you already have one, then FRP allows you to work with a value that changes over time, as if it does not change.

This is what it gives compared to callbacks:
')
1) Event stream and value changing in time (Property / Behavior) become objects of the first class . This means that they can be passed to functions and returned from functions.

For example, you can create an object containing clicks on a button (event stream), and then do everything with it that you can do with a regular variable — pass it to a function, return from a function, save as a property of another object, etc. Or you can create an object that reflects the current size of the browser window (a value that changes over time).

This makes it much better to share responsibility in code, divide it into modules, and write more flexible, short and manageable code.

For example, you can write a function that returns a drag stream (drag). As parameters, it will take 3 threads - the beginning of dragging, movement, end of dragging. Then you can transfer to this function: either streams for the corresponding mouse events (mousedown, mousemove, mouseup), or for touch events (touchstart, touchmove, touchend). The function itself will not know anything about the sources of events, but will work only with abstract streams. Bacon implementation example .

2) Explicit state

The second big advantage of FRP is its explicit state management. As you know, the state is one of the most important sources of program complexity, so proper management allows them to write more reliable and easy-to-support programs. An excellent report from Rich Hickey on the complexity (complexity) of "Simple Made Easy . "

FRP allows you to write most of the code on "pure functions" and control the data flow (dataflow) explicitly (using event streams), and store the states explicitly also in Property.



Kefir.js



Now there are two main FRP libraries for JavaScript, these are Bacon.js and RxJS . It seems to me that Bacon is closer to the spirit of functional programming, and RxJS is something from the world of OOP. Rx has very difficult documentation for perception - in the first place it’s a lot, and secondly it’s written in a very formal style (like auto-generated documentation from source code). Those. Rx is harder to learn and harder to use. But Rx is faster and consumes less memory.

The latter circumstance is sometimes the Achilles' heel of the Bacon. I first noticed a problem when I tried to write an analog scrollMonitor on Bacon. It turned out a very good API with all the power of FRP, but when I launched this stress test , everything just hung. As it turned out, Bacon consumes a lot of memory and frequent garbage collection causes friezes. This may be true for a large number of streams or on mobile devices. I believe that the library should have a greater margin of productivity in order to think less about it when writing application code!

Kefir.js is a new FRP library that I’ve been working on for the past few months. The Kefir API is very similar to the Bacon API, but in Kefir I pay a lot of attention to performance and memory consumption. Now Kefir is about 5-10 times faster than Bacon, and 1-2 times faster than Rx, and about the same with memory.

Kefir and Bacon performance comparison in live test . There are also results of synthetic memory tests . There are also synthetic performance tests, here are the results of some of them:

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 


I also try to make Kefir as easy to learn as possible, like Underscore or LoDash. Therefore, the documentation is very similar to the Underscore documentation. The goal is to make the documentation better than both in Rx and Bacon.

Another goal of Kefir is to rethink the Bacon API. Bacon has been developing for a long time and, due to the need to maintain backward compatibility, the API has become a bit clumsy in some places. Kefir has the opportunity to write everything from scratch, and I try to use this opportunity.

Current state


Kefir is currently in development, but there are a lot of things already and you can use it. The documentation is also not complete yet, but I hope to add it soon and keep it in good shape when adding new features.

Compared to Bacon, Kefir now lacks:



That's all I wanted to tell you about Kefir for now. I did not describe the library in detail, because Kefir is very similar to Bacon, and if you are familiar with the latter, you can easily master the first one. And if not, then you can study Kefir on Bacon tutorials, looking at the kefir documentation :-)

github.com/pozadi/kefir - a project on GitHub
pozadi.imtqy.com/kefir - documentation

Source: https://habr.com/ru/post/237495/


All Articles