Crate autofolder

source ·
Expand description

autofolder provides a single-element “folding” container that can be used to accumulate/select/etc. values in an ad-hoc fashion.

TL;DR: DynFolder example

use autofolder::*;

// Create an autofolder that retains the max u32 value:
let mut max = DynFolder::new(0_u32, std::cmp::max);

// We can "fold-in" individual items:
max.fold(3);

// We can then peek at the running output:
println!("Partial max is {}", max.as_ref());

// And still keep on folding by processing whole iterators:
max.extend((1..=5));

// And finally consume the autofolder to get the final output value:
println!("Max value is {}", max.into_inner());

See also Min, Max and MinMax for useful specific reducers.

Rationale

Folding in Rust is accomplished via the Iterator::fold method, like so:

iterator.fold(initial, function);
// (and this is all you can do)

That works well when all the data we need is provided by a single iterator. If we have a more complex logic, fold can’t be used.

autofolder flips this structure by being built with the initial value and the folding function, and accepting values from various types of different sources during its lifetime.

let mut autofolder = Autofolder::new(initial, function);
// Fold in a whole iterator, can be repeated:
autofolder.extend(iterator);
// Fold in an individual value:
autofolder.fold(value);

Types of folders

By binding strategy

This crate provides two types of autofolders with different function binding strategies:

  • DynFolder: the folding function is provided as a closure that is kept in a struct field. Characteristics:
    • Folding function can use any type, builtin or otherwise.
    • Each instance can use a different folding function, provided as a constructor argument. On the flip side, we can’t use DynFolder with .collect().
    • Slightly less efficient than ImplFolder due to the use of dynamic dispatch - we are effectively using a function pointer instead of a function call, after all.
  • ImplFolder: the folding function is implemented via a trait.
    • Folding function can only use types defined in the user crate, which is a limitation of using traits.
    • Each parameterized ImplFolder, defined by the pair of types, can only have one folding function. Because of that, we can use ImplFolder with .collect() if the output type implements Default
    • Slighly more efficient than DynFolder due to monomorphization, which turns .fold calls into direct function calls.

By aggregation strategy

Reduce in rust is a special kind of folding where the aggregator and the item types of the folding function are the same (Fn(Item, Item) -> Item). That allows us to set the internal autofolder state with the first yielded value, without calling the corresponding function.

This crate provides the following “autoreducer” types:

Specific autofolders

This create also provides some built-in autofolders for specific functions:

  • Min: container that keeps only the minimal value iterated, as given by std::cmp::PartialOrd.
  • Max: analogous to Max, but for the max value.
  • MinMax: container that keeps a tuple with both the min and max values.

Macros

Macro that implements FolderTrait with the provide closure.
Macro that implements ReduceTrait with the provide closure.

Structs

The DynFolder type uses a struct field for the folding function.
The DynReduce type uses a struct field for the folding function.
The ImplFolder type uses the FolderTrait for the folding function.
The ImplReduce type uses the ReduceTrait for the reduce function.
The Max type uses the std::cmp::PartialOrd trait to contain only the largest iterated value.
The Min type uses the std::cmp::PartialOrd trait to contain only the smallest iterated value.
The MinMax type uses the std::cmp::PartialOrd trait to contain both the smallest and largest iterated values.

Traits

Trait that provides the fold implementation for ImplFolder
Trait that provides the reduce implementation for ImplReduce