kompost 0.0.2

A crate to easen functional programming in rust by facilitating the creating composition of Iterator methods and anonymous Iterators—all without writing any trait or struct, without macros or unsafe code.
Documentation

Kompost

🚧🚧🚧🚧🚧 Under construction 🚧🚧🚧🚧🚧

A crate to ease functional programming in rust by facilitating the composability and re-usability of Iterator methods and anonymous Iterators—all without writing any trait or struct, without using macros or unsafe code.

Anonymous iterators

Adding a method to Iterator requires boilerplate in Rust—just have a look at [src/anonymous.rs]:

  • A Struct that holds a state (e.g., the parent iterator)
  • A Constructor
  • The implementation of Iterator for the struct
  • A trait that defines the method
  • A blanket implementation of that trait for all Iterators.

This crate provides convenience by offering anonymous Iterators that are defined by two closures only.

As all Iterators, they are defined by their behavior during creation and in the next method. The former is given by a closure that receives the current [Iterator] and returns a context structure. The second closure then computes the next value from the context and has mutable access to this context. Hence, the anonymous() method strongly resembles Iterator::scan() with the notable exception that the first parameter is a closure.

use kompost::*;

assert_eq!(
    vec![1,2,3]
        .into_iter()
        .anonymous(
            |it| it.into_iter(),
            |it| it.next()
        )
        .collect::<Vec<_>>(),
    vec![1,2,3]
);

A slightly more complex idea is to collect the iterator first and define a custom behavior.

assert_eq!(
    [1, 2, 3]
        .iter()                      // Don't consume
        .anonymous(
            |it| it
                .into_iter()
                .rev()               // Revert
                .copied()            
                .collect::<Vec<_>>()
                .into_iter(),        // We need an iterator in next
            |it| it.next().map(|x| x + 4)
        )
        .collect::<Vec<_>>(),
    vec![7, 6, 5]
);

This enables more complex tasks. For instance, we can transpose a nested iterable structure ([IntoIterator]<Item = [IntoIterator]<_>>) without the need of writing a single struct or trait! The example is annotated with the inline type hints as shown by rust-analyzer lsp:

let x: Vec<_> = [1, 2, 3, 4]                 // An array in row-major order
    .chunks(2)                               // Nested iterable: Chunks<i32>
    .anonymous(
        |chunks| chunks.map(|row| row.iter()).collect::<Vec<_>>(),
        |context| {
            let transposed = context         // &mut Vec<Iter,i32>
                .iter_mut()
                .filter_map(|i| i.next())    // impl Iterator<Item = &i32>
                .collect::<Vec<_>>();        // Vec<&i32>
                                             // If the iterators over the rows return `None`, transpose is empty
            if transposed.is_empty() {
                None
            } else {
                Some(transposed.into_iter())
            }
        },
    )                                        // AnonymousIterator
    .flatten()                               // impl Iterator<Item = &i32>
    .copied()                                // impl Iterator<Item = i32>
    .collect();
assert_eq!(x, [1, 3, 2, 4]);

This can be conveniently "bundled" in a function—transpose() to be used with the composed() from this crate.

Composed iterators

Iterator composition allows defining reusable groups of frequently used combinations of [Iterator] methods (such as map or scan) that can be easily tested.

A very simple example might look like:

use kompost::ComposedIterable;
    fn favorite_pipeline(it: impl Iterator<Item = i32>) -> impl Iterator<Item = f64> {
    it.skip(5)
        .map(|x| x.pow(2))
        .take_while(|x| *x < 100)
        .map(|x| x as f64)
}
assert_eq!(
    [1, 2, 3, 4, 5, 6, 7].into_iter().composed(favorite_pipeline).collect::<Vec<_>>(),
    vec![36.0f64, 49.0]
)

Kompost comes with a few useful (at least to me), predefined compound functions such as transpose and periodic_windows().

The letter shows a few interesting aspects: How a compound function can accept an additional parameter, how stricter type restrictions can be enforced (ExactSizeIterator), and a more advanced showcase of the anonymous() method.

pub fn periodic_windows<T>(
    size: usize,
    it: impl ExactSizeIterator<Item = T> + Clone,
) -> impl Iterator<Item = impl Iterator<Item = T>> {
    it.anonymous(
        |it| {
            let len = it.len();
            (0usize, len, it.cycle())
        },
        move |(i, len, it)| {
            let window = it.clone();
            it.next();
            *i += 1;
            if i <= len {
                Some(window.take(size))
            } else {
                None
            }
        },
    )
}

The compound function can then be easily applied:

let size=3;
let x = [1, 2, 3, 4].into_iter()
    .composed(|i| periodic_windows(3, i))
    .flatten()
    .collect::<Vec<_>>();
assert_eq!(x, [1,2,3,2,3,4,3,4,1,4,1,2])