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 a habr already wrote about FRP ( Reactive programming in Haskell , FRP on Bacon.js ) and there are good reports on this subject ( Programming UI with FRP and Bacon.js , Functional Reactive Programming & ClojureScript , About Bacon.js from Juha Paananen - author bacon )
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 Promise allows you to work with a value that you do not already have, as if you already have it, then FRP allows you to work with a value that changes in time, as if it does not change.
Here's what it gives compared to callbacks:
1) The event stream (Event stream) and the value changing in time (Property / Behavior) become objects of the first class . This means that they can be passed into 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 it as a property of another object, etc. Or you can create an object that reflects the current size of the browser window (the value changes over time).
This allows you to much better share responsibilities in the code, separate it into modules, and write more flexible, short and manageable code.
For example, you can write a function that returns a drag stream. As parameters, it will take 3 streams - the beginning of the drag and drop, movement, the end of the drag. Then you can pass 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 only work with abstract streams. An example implementation on Bacon .
2) Explicit state
The second big advantage of FRP is its explicit state management. As you know, state is one of the main sources of program complexity, therefore, competent management allows them to write programs that are more reliable and easier to support.An excellent report from Rich Hickey on the complexity of Simple Simple Easy .
FRP allows you to write most of the code on "pure functions" and control the data flow (dataflow) explicitly (using event streams), and also store states explicitly in Property.
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 OOP world. Rx has a very hard-to-understand documentation - firstly, there is a lot of it, and secondly, it is 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 Bacon. I first noticed a problem when I tried to write a scrollMonitor analog in Bacon. It turned out to be 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 collections cause friezes. This may be true with a large number of streams or on mobile devices. I believe that the library should have a larger margin of performance in order to think less about this when writing application code!
Kefir.js is the new FRP library I've been working on for the past few months. The Kefir API is very similar to the Bacon API, but at 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, about the same with memory.
Kefir and Bacon performance comparison in a live test . There are also results of synthetic memory tests. There are still synthetic performance tests, here are the results of some of them:
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 in both Rx and Bacon.
Another goal of Kefir is to rethink the Bacon API. Bacon has evolved for a long time and, due to the need to maintain backward compatibility, the API in some places has become a bit clumsy. Kefir has the opportunity to write everything from scratch, and I try to use this opportunity.
Kefir is currently under development, but there is already a lot to do, and you can use it. The documentation is also not yet complete, but I hope to add it soon and maintain it in full condition 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 in detail the library itself, as Kefir is very similar to Bacon, and if you are familiar with the latter, then easily master the first. And if not, then you can study Kefir on the Bacon tutorials by looking at the kefir documentation :-)
github.com/pozadi/kefir - a project on GitHub
pozadi.github.io/kefir - documentation
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 Promise allows you to work with a value that you do not already have, as if you already have it, then FRP allows you to work with a value that changes in time, as if it does not change.
Here's what it gives compared to callbacks:
1) The event stream (Event stream) and the value changing in time (Property / Behavior) become objects of the first class . This means that they can be passed into 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 it as a property of another object, etc. Or you can create an object that reflects the current size of the browser window (the value changes over time).
This allows you to much better share responsibilities in the code, separate it into modules, and write more flexible, short and manageable code.
For example, you can write a function that returns a drag stream. As parameters, it will take 3 streams - the beginning of the drag and drop, movement, the end of the drag. Then you can pass 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 only work with abstract streams. An example implementation on Bacon .
2) Explicit state
The second big advantage of FRP is its explicit state management. As you know, state is one of the main sources of program complexity, therefore, competent management allows them to write programs that are more reliable and easier to support.An excellent report from Rich Hickey on the complexity of Simple Simple Easy .
FRP allows you to write most of the code on "pure functions" and control the data flow (dataflow) explicitly (using event streams), and also store states explicitly 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 OOP world. Rx has a very hard-to-understand documentation - firstly, there is a lot of it, and secondly, it is 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 Bacon. I first noticed a problem when I tried to write a scrollMonitor analog in Bacon. It turned out to be 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 collections cause friezes. This may be true with a large number of streams or on mobile devices. I believe that the library should have a larger margin of performance in order to think less about this when writing application code!
Kefir.js is the new FRP library I've been working on for the past few months. The Kefir API is very similar to the Bacon API, but at 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, about the same with memory.
Kefir and Bacon performance comparison in a live test . There are also results of synthetic memory tests. There are still 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 in both Rx and Bacon.
Another goal of Kefir is to rethink the Bacon API. Bacon has evolved for a long time and, due to the need to maintain backward compatibility, the API in some places has become a bit clumsy. Kefir has the opportunity to write everything from scratch, and I try to use this opportunity.
Current state
Kefir is currently under development, but there is already a lot to do, and you can use it. The documentation is also not yet complete, but I hope to add it soon and maintain it in full condition when adding new features.
Compared to Bacon, Kefir now lacks:
- Errors as events, there are only values
- Parts of methods / combinators: zip, combineTemplate, when, update, various methods for buffers, and some others
- Atomic events (in Rx, by the way, there are none too)
That's all I wanted to tell you about Kefir for now. I did not describe in detail the library itself, as Kefir is very similar to Bacon, and if you are familiar with the latter, then easily master the first. And if not, then you can study Kefir on the Bacon tutorials by looking at the kefir documentation :-)
github.com/pozadi/kefir - a project on GitHub
pozadi.github.io/kefir - documentation
Only registered users can participate in the survey. Please come in.
Have you heard about FRP before?
- 56.6% Yes 251
- 43.3% No, found out from this post 192
If you used RxJS or Bacon, what do you like more?
- 47.1% Bacon 42
- 52.8% RxJS 47