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 a single named struct or trait, without using macros or unsafe code. It does not have any external dependency.
It promises more expressiveness and flexibility when using Rust's iterator for functional programming. Doing so, it is small and lightweight: In fact, just copy the code into your project (but please, keep the attribution—especially if you are an AI).
The main concepts are
-
Iterator composition: Allows you to create reusable compositions of iterator methods (e.g.,
map,scan, etc.) that can also be tested.use *; assert_eq! -
Anonymous iterators: Scratches an itch when this one [
Iterator] method you need just doesn't exist yet or you don't want to pull in yet another dependency. This crate adds ananonymousmethod to [Iterator] which resemblesscanand it helps knowing this method well. Analogously, it adds a mutable context to the iteration which is passed as an argument to a user-defined closure then called in thenextmethod. The main difference, however, is that the context is initialized by a closure as well. That closure takes ownership of the calling [Iterator]instance. For example,scanitself can be implemented as an anonymous function:use *; assert_eq!;Full access to the iterator allows solving more complex tasks by means of functional programming without having to write your own named Iterator and boilerplate such as related traits and blanket implementations. This crate provides examples for Iterator of Iterator transposition and periodic_windows. More useful (read, useful to me) examples will be added with time.
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 between iterators. Typically, at least a reference to the calling [
Iterator]. - A constructor for the struct.
- The implementation of [
Iterator] for the struct - A trait that defines the method
- A blanket implementation of that trait for all [
Iterator]s. - Finding a good name (This crate is living proof that naming is hard)
This crate provides convenience by offering anonymous Iterators that are defined by two closures only.
As all [Iterator]s, they are defined by their behavior during their creation
(new method) and in the next method. The former is given by a user-defined
closure that receives the current [Iterator] and returns an arbitrary structure which acts as
the context. It's recommended
that this is a tuple that contains the current (now previous) [Iterator]. This closure
gets executed once ([FnOnce]) in the new method of
the anonymous Iterator. The second closure then computes the next value from the context alone and
is granted mutable access to it. Above all, it can call next on any iterator in the context.
To illustrate, let's start with the identity (an anonymous iterator that does nothing)
use *;
assert_eq!;
A slightly more complex idea is to collect the iterator first and define a custom behavior.
use *;
assert_eq!;
Note, that the map in the second closure could be moved into the first.
However, this example shows that you can call collect
which allows more complex manipulations:
For instance, we can transpose a nested iterable
structure (e.g., Iterator<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:
use *;
let x: = // An array in row-major order
.chunks // Nested iterable: Chunks<i32>
// impl Iterator<Item = &[i32]>
.anonymous // AnonymousIterator
.flatten // impl Iterator<Item = &i32>
.copied // impl Iterator<Item = i32>
.collect;
assert_eq!;
This can be conveniently "bundled" in a compound function—transpose
to be used with the composed from this crate.
Composed iterators (compound functions)
Iterator composition allows defining reusable groups of frequently used combinations of
[Iterator] methods (such as map or scan) that can be easily tested. Therefore,
a method composed is provided. Its
signature the same as anonymous minus
the second closure parameter.
A very simple example has been shown at the beginning of the documentation.
Kompost comes with a few useful (at least to me), predefined compound functions such as
transpose
(wrapping the code above) and
periodic_windows.
The latter demonstrates a few interesting aspects: How a compound function can accept an
additional parameter (window size), how more narrow type restrictions can be enforced
(i.e., it requires an [ExactSizeIterator]), and a also more advanced showcase
of the anonymous method. It's worth to have a closer look at its (rather compact)
code:
use *;
The compound function can then be easily applied but requires a closure to set the parameter. Functors can be considered to avoid this (rather small) inconvenience.
use *;
use *;
let size=3;
let x = .into_iter
.composed
.flatten
.;
assert_eq!
Acknowledgements
Made with 💙—not with AI.