Crate carboxyl + + [−] + + [src]
+Carboxyl provides primitives for functional reactive programming in Rust. +It draws inspiration from the Sodium libraries and Push-Pull FRP, +as described by Elliott (2009).
+ +Overview
+Functional reactive programming (FRP) is a composable and modular +abstraction for creating dynamic and reactive systems. In its most general +form it models these systems as a composition of two basic primitives: +streams are a series of singular events and signals are continuously +changing values.
+ +Carboxyl is an imperative, hybrid push- and pull-based implementation of +FRP. Streams and the discrete components of signals are data-driven, i.e. +whenever an event occurs the resulting changes are propagated to everything +that depends on it.
+ +However, the continuous components of signals are demand-driven. Internally, +Carboxyl stores the state of a signal as a function. This function has to +be evaluated by consumers of a signal to obtain a concrete value.
+ +Nonetheless, Carboxyl has no explicit notion of time. Its signals are +functions that can be evaluated at any time, but they do not carry any +inherent notion of time. Synchronization and atomicity is achieved by a +transaction system.
+ +Functional reactive primitives
+This library provides two basic types: Stream
and Signal
. A stream is a
+discrete sequence of events, a signal is a container for values that change
+(discretely) over time.
The FRP primitives are mostly implemented as methods of the basic types to +ease method chaining, except for the various lifting functions, as they do +not really belong to any type in particular.
+ +In addition, the Sink
type allows one to create a stream of events by
+sending values into it. It is the only way to create a stream from scratch,
+i.e. without using any of the other primitives.
Usage example
+Here is a simple example of how you can use the primitives provided by +Carboxyl. First of all, events can be sent into a sink. From a sink one +can create a stream of events. Streams can also be filtered, mapped and +merged. One can e.g. hold the last event from a stream as a signal.
++use carboxyl::Sink; + +let sink = Sink::new(); +let stream = sink.stream(); +let signal = stream.hold(3); + +// The current value of the signal is initially 3 +assert_eq!(signal.sample(), 3); + +// When we fire an event, the signal get updated accordingly +sink.send(5); +assert_eq!(signal.sample(), 5); ++ +
One can also directly iterate over the stream instead of holding it in a +signal:
++let mut events = stream.events(); +sink.send(4); +assert_eq!(events.next(), Some(4)); ++ +
Streams and signals can be combined using various primitives. We can map a +stream to another stream using a function:
++let squares = stream.map(|x| x * x).hold(0); +sink.send(4); +assert_eq!(squares.sample(), 16); ++ +
Or we can filter a stream to create a new one that only contains events that +satisfy a certain predicate:
++let negatives = stream.filter(|&x| x < 0).hold(0); + +// This won't arrive at the signal. +sink.send(4); +assert_eq!(negatives.sample(), 0); + +// But this will! +sink.send(-3); +assert_eq!(negatives.sample(), -3); ++ +
There are some other methods on streams and signals, that you can find in +their respective APIs.
+ +Note that all these objects are Send + Sync + Clone
. This means you can
+easily pass them around in your code, make clones, give them to another
+thread, and they will still be updated correctly.
You may have noticed that certain primitives take a function as an argument. +There is a limitation on what kind of functions can and should be used here. +In general, as FRP provides an abstraction around mutable state, they should +be pure functions (i.e. free of side effects).
+ +For the most part this is guaranteed by Rust's type system. A static
+function with a matching signature always works. A closure though is very
+restricted: it must not borrow its environment, as it is impossible to
+satisfy the lifetime requirements for that. So you can only move stuff into
+it from the environment. However, the moved contents of the closure may also
+not be altered, which is guaranteed by the Fn(…) -> …)
trait bound.
However, both closures and functions could still have side effects such as
+I/O, changing mutable state via Mutex
or RefCell
, etc. While Rust's type
+system cannot prevent this, you should generally not pass such functions to
+the FRP primitives, as they break the benefits you get from using FRP.
+(An exception here is debugging output.)
Modules
+lift | +
+ Lifting of n-ary functions. + + |
+
Macros
+lift! | ++ + | +
Structs
+Signal | +
+ A continuous signal that changes over time. + + |
+
SignalMut | +
+ Signal variant using inner mutability for efficient in-place updates. + + |
+
Sink | +
+ An event sink. + + |
+
Stream | +
+ A stream of events. + + |
+