Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/shanecelis/frayed
Unfused and unashamed iterators under the hood, conventional iterators out the door
https://github.com/shanecelis/frayed
Last synced: 2 months ago
JSON representation
Unfused and unashamed iterators under the hood, conventional iterators out the door
- Host: GitHub
- URL: https://github.com/shanecelis/frayed
- Owner: shanecelis
- License: apache-2.0
- Created: 2024-03-01T21:09:53.000Z (11 months ago)
- Default Branch: master
- Last Pushed: 2024-03-14T09:40:23.000Z (10 months ago)
- Last Synced: 2024-03-15T10:07:50.763Z (10 months ago)
- Language: Rust
- Homepage:
- Size: 43 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE2
Awesome Lists containing this project
README
# Frayed
![Maintenance](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
[![crates-io](https://img.shields.io/crates/v/frayed.svg)](https://crates.io/crates/frayed)
[![api-docs](https://docs.rs/frayed/badge.svg)](https://docs.rs/frayed)Unfused and unashamed iterators under the hood, conventional iterators out the
door# Introduction
Rust iterators[^0] come in a few varieties: fused, unfused, and now frayed. The variety
is determined by how it behaves after `.next()` returns `None`.```rust ignore
pub trait Iterator {
type Item;
fn next(&mut self) -> Option;
}
```## Fused
A fused iterator once `.next()` returns `None` will only ever return `None`. It
is said to be exhausted at the first `None`. This behavior can be guaranteed
with the [`.fuse()`][fuse] method that returns a [std::iter::Fuse][Fuse] iterator.## Unfused
The majority of iterators are unfused. They have no programmatic guarantees like
[Fuse][Fuse] but the producers and consumers have tacitly agreed that it's
impolite to ask `.next()` of an iterator who has already said `None`. It is
expected to be exhausted at the first `None`.## Frayed
Frayed iterators delight in furnishing further elements after returning `None`.
They can economically represent multiple sequences. They are not indefatigably
barbaric, however; a frayed iterator is expected to be exhausted when it returns
two `None`s consecutively.# Usage
Suppose we have an iterator that represents multiple sequences. For instance
`SevenIter`is a unfused iterator that represents these sequences: `[1, 2]`, `[4,
5]`, and `[7]`.```rust compile
use frayed::{Frayed, FrayedTools, FraughtTools};/// This iterator skips every number divisible by 3 up to seven.
struct SevenIter(u8);/// SevenIter's .next() returns Some(1), Some(2), None, Some(4), Some(5), None,
/// Some(7), None, None, and so on.
impl Iterator for SevenIter {
type Item = u8;
fn next(&mut self) -> Option {
self.0 += 1;
(self.0 % 3 != 0 && self.0 <= 7).then_some(self.0)
}
}/// Mark iterator with `.frayed() or impl the `Frayed` marker trait.
let frayed_iter = SevenIter(0).frayed();
/// Defray the frayed iterator into an iterator of iterators.
for subiter in &frayed_iter.defray() {
for i in subiter {
print!("{i} ");
}
println!()
}
```The above will produce this output
```text
1 2
4 5
7
```# Extensions
## FrayedTools
`FrayedTools` extends iterators marked `Frayed`.
## FraughtTools
`FraughtTools` extends regular iterators but often either accepts frayed
iterators as arguments or returns frayed iterators.# Q&A
## Why restrict FrayedTools to only Frayed iterators?
Because dealing with an iterator that conceptually represents many iterators is
confusing: Sometimes you want to map over all the elements. Sometimes you want
to map over the subsequences. This way the compiler can help us.For instance if we forget to mark our iterator as "frayed", we can't "defray" it.
```rust compile_fail
let frayed_iter = SevenIter(0); // .frayed();
let _ = &frayed_iter.defray(); // Not marked frayed. No `defray()` method.
```## Defray isn't an iterator. How can I "map" over it?
`Defray` implements `IntoIterator` for `&Defray`. The problems appear when one
wants to return a `Defray` but say `.map()` its output. If one can do what they
need with the underlying frayed iterator, use `defrayed.into_inner()` to
retrieve it, process it, and consider `.defray()`-ing it again.If one wants to process the iterators and not the underlying elements, that
remains an open question as to how best to do that. Perhaps `Defray` itself
could provide a `map(self, f: F) -> Map>, F, Y> where
F: FnMut(Iter) -> Y)` function.# Motivation
When writing an iterator implementation by hand that represents multiple
sequences, it is usually easy to write a `Iterator>` even though
allocating and collecting a `Vec` is not essential or desired. However, writing a
`Iterator>` requires tricky machinery[^1] if one hopes to
respect the API that entails, e.g., the `SubIter`s can be consumed out of order
or dropped.If one writes instead a "frayed" iterator `Iterator`—where the
`None` represents the end of a subsequence not the end of the iterator—that is
often much easier. One can consume these iterators with a some care but they
remain unconventional and surprising.```rust compile
fn raw_consume_unfused(frayed: impl Iterator) {
let mut frayed = frayed.peekable();
loop {
// Consume the subsequence.
for i in frayed.by_ref() {
print!("{} ", i);
}
println!();
// Check for second None that means frayed iterator is exhausted.
if frayed.peek().is_none() {
break;
}
}
}
```The initial motivation of this crate is to make it easy for "frayed" iterators
to be consumed by the uninitiated. Consider instead this code:```rust compile
use frayed::*;
fn raw_consume_frayed(frayed: impl Iterator + Frayed) {
for subiter in &frayed.defray() {
for i in subiter {
print!("{} ", i);
}
}
}
```But it would be even better if producers kept their frayed iterators under the
covers and then exposed the abstractions that we're all used to.```rust ignore
fn chunks(&self) -> frayed::Defray {
// ...
}fn main() {
let obj = ...;
for subiter in &obj.chunks() {
for i in subiter {
print!("{} ", i);
}
}
}```
[Fuse]: https://doc.rust-lang.org/std/iter/struct.Fuse.html
[fuse]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fuse[^0]: Rust iterators have a wonderfully succinct trait.
[^1]: See [GroupBy](https://docs.rs/itertools/latest/itertools/structs/struct.GroupBy.html) implementation in itertools for an example.