reactive_rs/lib.rs
1//! This crate provides the building blocks for functional reactive programming (FRP)
2//! in Rust. It is inspired by
3//! [carboxyl](https://crates.io/crates/carboxyl),
4//! [frappe](https://crates.io/crates/frappe)
5//! and [bidule](https://crates.io/crates/bidule) crates, and
6//! various [ReactiveX](http://reactivex.io/) implementations.
7//!
8//! # Overview
9//!
10//! ## Purpose
11//!
12//! The main use case of this library is to simplify creating efficient
13//! computational DAGs (or computational trees, to be precise) that operate
14//! on streams of values. It does not aim to replicate the entire galaxy of
15//! ReactiveX operators, nor does it attempt to delve into
16//! futures/concurrency territory.
17//!
18//! What is a computational tree? First, there's the root at the top, that's
19//! where the input values get fed into continuously. Then, we perform
20//! computations on these values – each of which may yield zero,
21//! one or more values that are sent further down. Some downstream
22//! nodes may share their parents – for instance, `g(f(x))` and `h(f(x))`, where `x` is
23//! the input and `f` is the intermediate transformation; in this case, we want
24//! to make sure we don't have to recompute `f(x)` twice. Moreover, this
25//! being Rust, we'd like to ensure we're not copying and cloning any values
26//! needlessly, and we generally prefer things to be zero-cost/inlineable
27//! when possible. Finally, there are leaves – these are observers, functions
28//! that receive transform values and do something with them, likely recording
29//! them somewhere or mutating the environment in some other way.
30//!
31//! ## Terminology
32//!
33//! - *Observer* - a function that accepts a value and returns nothing (it will
34//! most often that note mutate the external environment in some way).
35//! - *Stream* - an object can be subscribed to by passing an observer to it.
36//! Subscribing to a stream consumes the stream, thus at most one observer
37//! can ever be attached to a given stream.
38//! - *Broadcast* - an observer that owns a collection of other observers and
39//! transmits its input to each one of them sequentially. A broadcast can
40//! produce new streams, *subscriptions*, each one receiving the same input
41//! as the broadcast itself. Subscription is a stream that adds its
42//! subscribers to the broadcast's collection when being subscribed to.
43//!
44//! ## Context
45//!
46//! Streams, broadcasts and observers in this crate operate on pairs of
47//! values: the *context* and the *element*. Context can be viewed as
48//! optional metadata attached to the original value. Closures required in
49//! methods like `.map()` only take one argument (the element) and are
50//! expected to return a single value; this way, the element can be changed
51//! without touching the context. This can be extremely convenient if you
52//! need to access the original input value (or any "upstream" value) way
53//! down the computation chain – this way you don't have to propagate
54//! it explicitly.
55//!
56//! Most stream/broadcast methods have an alternative "full" version that
57//! operates on both context/element, with `_ctx` suffix.
58//!
59//! ## Examples
60//!
61//! Consider the following problem: we have an incoming stream of
62//! buy/sell price pairs, and for each incoming event we would like to
63//! compute how the current mid-price (the average between the two)
64//! compares relatively to the minimum buy price and the maximum sell
65//! price over the last three observations. Moreover, we would like to
66//! skip the first few events in order to allow the buffer to fill up.
67//!
68//! Here's one way we could do it (not the most ultimately efficient
69//! way of solving this particular problem, but it serves quite well
70//! to demonstrate the basic functionality of the crate):
71//!
72//! ```
73//! use std::cell::Cell;
74//! use std::f64;
75//! use reactive_rs::*;
76//!
77//! let min_rel = Cell::new(0.);
78//! let max_rel = Cell::new(0.);
79//!
80//! // create a broadcast of (buy, sell) pairs
81//! let quotes = SimpleBroadcast::new();
82//!
83//! // clone the broadcast so we can feed values to it later
84//! let last = quotes.clone()
85//! // save the mid-price for later use
86//! .with_ctx_map(|_, &(buy, sell)| (buy + sell) / 2.)
87//! // cache the last three observations
88//! .last_n(3)
89//! // wait until the queue fills up
90//! .filter(|quotes| quotes.len() > 2)
91//! // share the output (slices of values)
92//! .broadcast();
93//!
94//!// subscribe to the stream of slices
95//! let min = last.clone()
96//! // compute min buy price
97//! .map(|p| p.iter().map(|q| q.0).fold(1./0., f64::min));
98//!// subscribe to the stream of slices
99//! let max = last.clone()
100//! // compute max sell price
101//! .map(|p| p.iter().map(|q| q.1).fold(-1./0., f64::max));
102//!
103//! // finally, attach observers
104//! min.subscribe_ctx(|p, min| min_rel.set(min / p));
105//! max.subscribe_ctx(|p, max| max_rel.set(max / p));
106//!
107//! quotes.send((100., 102.));
108//! quotes.send((101., 103.));
109//! assert_eq!((min_rel.get(), max_rel.get()), (0., 0.));
110//! quotes.send((99., 101.));
111//! assert_eq!((min_rel.get(), max_rel.get()), (0.99, 1.03));
112//! quotes.send((97., 103.));
113//! assert_eq!((min_rel.get(), max_rel.get()), (0.97, 1.03));
114//! ```
115//!
116//! # Lifetimes
117//!
118//! Many `Stream` trait methods accept mutable closures; observers are
119//! also essentially just closures, and they are the only way you can
120//! get results from the stream out into the environment. Rest assured,
121//! at some point you'll run into lifetime problems (this being Rust,
122//! it's pretty much certain).
123//!
124//! Here's the main rule: lifetimes of observers (that is, lifetimes of
125//! what they capture, if anything) may not be shorter than the lifetime of
126//! the stream object. Same applies to lifetimes of closures in methods
127//! like `.map()`.
128//!
129//! In some situations it' tough to prove to the compiler you're doing
130//! something sane, in which case arena-based allocators (like
131//! [`typed-arena`](https://crates.io/crates/typed-arena)) may be
132//! of great help – allowing you to tie lifetimes of a bunch of
133//! objects together, ensuring simultaneous deallocation.
134
135#![crate_type = "lib"]
136#![warn(missing_docs)]
137
138#[cfg(any(test, feature = "slice-deque"))]
139extern crate slice_deque;
140
141pub use stream::{Broadcast, SimpleBroadcast, Stream};
142
143mod stream;