Skip to main content

orlando_transducers/
lib.rs

1//! # Orlando: Compositional Data Transformation
2//!
3//! Orlando is a high-performance data transformation library that implements transducers
4//! in Rust, compiling to WebAssembly for use in JavaScript applications.
5//!
6//! ## What are Transducers?
7//!
8//! Transducers compose transformations, not data. Instead of creating intermediate collections:
9//!
10//! ```text
11//! data → map → [intermediate] → filter → [intermediate] → result
12//! ```
13//!
14//! We compose operations first, then execute in a single pass:
15//!
16//! ```text
17//! (map ∘ filter) → data → result
18//! ```
19//!
20//! This approach:
21//! - **Eliminates intermediate allocations** - No temporary arrays between operations
22//! - **Enables early termination** - Operations like `take` can stop processing immediately
23//! - **Composes efficiently** - Build complex pipelines from simple parts
24//! - **Executes in a single pass** - Process each element only once
25//!
26//! ## Category Theory Foundation
27//!
28//! Transducers are natural transformations between fold functors. Given a reducing
29//! function `R: (Acc, Out) -> Acc`, a transducer transforms it into a new reducing
30//! function `(Acc, In) -> Acc`.
31//!
32//! Formally, a transducer is a polymorphic function:
33//!
34//! ```text
35//! ∀Acc. ((Acc, Out) -> Acc) -> ((Acc, In) -> Acc)
36//! ```
37//!
38//! This mathematical foundation ensures that transducers compose correctly and
39//! satisfy important laws like associativity and identity.
40//!
41//! ## Usage (Rust)
42//!
43//! ```rust
44//! use orlando_transducers::transforms::{Map, Filter, Take};
45//! use orlando_transducers::collectors::to_vec;
46//! use orlando_transducers::transducer::Transducer;
47//!
48//! // Build a pipeline
49//! let pipeline = Map::new(|x: i32| x * 2)
50//!     .compose(Filter::new(|x: &i32| x % 3 == 0))
51//!     .compose(Take::new(5));
52//!
53//! // Execute in a single pass
54//! let result = to_vec(&pipeline, 1..100);
55//! // result: [6, 12, 18, 24, 30]
56//! ```
57//!
58//! ## Usage (JavaScript via WASM)
59//!
60//! ```javascript
61//! import { Pipeline } from './pkg/orlando.js';
62//!
63//! const pipeline = new Pipeline()
64//!   .map(x => x * 2)
65//!   .filter(x => x % 3 === 0)
66//!   .take(5);
67//!
68//! const result = pipeline.toArray([...Array(100).keys()].map(x => x + 1));
69//! // result: [6, 12, 18, 24, 30]
70//! ```
71//!
72//! ## Performance
73//!
74//! Orlando leverages:
75//! - **Zero-cost abstractions** - Rust's monomorphization eliminates abstraction overhead
76//! - **WASM SIMD** - Vectorized operations for numeric data
77//! - **Early termination** - Stop processing as soon as possible
78//! - **Single-pass execution** - No intermediate allocations
79//!
80//! Benchmarks show 3-5x performance improvement over pure JavaScript array chaining.
81
82pub mod collectors;
83pub mod geometric_optics;
84pub mod iter_ext;
85pub mod logic;
86pub mod optics;
87pub mod profunctor;
88pub mod signal;
89pub mod simd;
90pub mod step;
91pub mod stream;
92pub mod transducer;
93pub mod transforms;
94
95#[cfg(target_arch = "wasm32")]
96pub mod pipeline;
97
98#[cfg(target_arch = "wasm32")]
99pub mod geometric_optics_wasm;
100
101#[cfg(target_arch = "wasm32")]
102pub mod optics_wasm;
103
104// Re-export main types for convenience
105pub use step::{cont, is_stopped, stop, unwrap_step, Step};
106pub use transducer::{Compose, Identity, Transducer};
107
108// Re-export common transforms
109pub use transforms::{
110    Aperture, Chunk, Drop, DropWhile, Filter, FlatMap, Interpose, Map, Reject, RepeatEach, Scan,
111    Take, TakeWhile, Tap, Unique, UniqueBy,
112};
113
114// Re-export collectors
115pub use collectors::{
116    cartesian_product, contains, count, cycle, difference, drop_last, every, find, first,
117    frequencies, group_by, intersection, last, max, max_by, mean, median, merge, min, min_by, mode,
118    none, partition, partition_by, product, quantile, range, reduce, repeat, reservoir_sample,
119    reverse, some, sort_by, sort_with, std_dev, sum, symmetric_difference, take_last, to_vec,
120    top_k, unfold, union, variance, zip, zip_longest, zip_with,
121};
122
123// Re-export logic functions and conditional transducers
124pub use logic::{all_pass, any_pass, both, complement, either, IfElse, Unless, When};
125
126// Re-export optics
127pub use optics::{ComposedLens, Fold, Iso, Lens, Optional, Prism, Traversal};
128
129// Re-export additional optic types from Karpal
130pub use karpal_optics::{Getter, Review, Setter};
131
132// Re-export geometric optics
133pub use geometric_optics::{
134    blade_grade, blades_at_grade_count, component_get, component_set, grade_extract, grade_indices,
135    grade_involution, grade_mask, grade_project, grade_project_max, has_grade, is_pure_grade, norm,
136    norm_squared, normalize,
137};
138
139#[cfg(target_arch = "wasm32")]
140pub use pipeline::Pipeline;
141
142#[cfg(target_arch = "wasm32")]
143pub use geometric_optics_wasm::{
144    blade_grade as wasm_blade_grade, blades_at_grade_count as wasm_blades_at_grade_count,
145    component_get as wasm_component_get, component_set as wasm_component_set,
146    grade_extract as wasm_grade_extract, grade_indices as wasm_grade_indices,
147    grade_involution as wasm_grade_involution, grade_mask as wasm_grade_mask,
148    grade_project as wasm_grade_project, grade_project_max as wasm_grade_project_max,
149    has_grade as wasm_has_grade, is_pure_grade as wasm_is_pure_grade, mv_norm, mv_norm_squared,
150    mv_normalize, mv_reverse,
151};
152
153#[cfg(target_arch = "wasm32")]
154pub use optics_wasm::{
155    fold, iso, lens, lens_path, optional, prism, traversal, JsFold, JsIso, JsLens, JsOptional,
156    JsPrism, JsTraversal,
157};
158
159// WASM initialization
160#[cfg(target_arch = "wasm32")]
161use wasm_bindgen::prelude::*;
162
163#[cfg(all(target_arch = "wasm32", not(test)))]
164#[wasm_bindgen(start)]
165pub fn main() {
166    // WASM initialization
167    // Future: Add console_error_panic_hook feature for better debugging
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_basic_pipeline() {
176        let pipeline = Map::new(|x: i32| x * 2)
177            .compose(Filter::new(|x: &i32| x % 3 == 0))
178            .compose(Take::new(5));
179
180        let result = to_vec(&pipeline, 1..100);
181        assert_eq!(result, vec![6, 12, 18, 24, 30]);
182    }
183
184    #[test]
185    fn test_early_termination() {
186        // Take should stop early, not process all 1 million elements
187        let pipeline = Take::<i32>::new(3);
188        let result = to_vec(&pipeline, 1..1_000_000);
189        assert_eq!(result, vec![1, 2, 3]);
190    }
191
192    #[test]
193    fn test_composition_laws() {
194        // Identity law: id ∘ f = f ∘ id = f
195        let f = Map::new(|x: i32| x * 2);
196        let id = Identity::<i32>::new();
197
198        let left = id.compose(Map::new(|x: i32| x * 2));
199        let right = Map::new(|x: i32| x * 2).compose(Identity::<i32>::new());
200
201        let data = vec![1, 2, 3, 4, 5];
202        assert_eq!(to_vec(&left, data.clone()), to_vec(&f, data.clone()));
203        assert_eq!(to_vec(&right, data.clone()), to_vec(&f, data.clone()));
204    }
205
206    #[test]
207    fn test_no_intermediate_allocations() {
208        // This pipeline should execute in a single pass
209        // without creating intermediate vectors
210        let pipeline = Map::new(|x: i32| x * 2)
211            .compose(Filter::new(|x: &i32| *x > 5))
212            .compose(Map::new(|x: i32| x + 1))
213            .compose(Take::new(3));
214
215        let result = to_vec(&pipeline, 1..100);
216        assert_eq!(result, vec![7, 9, 11]);
217    }
218}