tcbrindle / flux

A C++20 library for sequence-orientated programming
https://tristanbrindle.com/flux/
Boost Software License 1.0
476 stars 29 forks source link

Simplify element access in pipeline #193

Open Guekka opened 1 month ago

Guekka commented 1 month ago

Description

Currently, flux::first, flux::find_if and some others return a cursor. I will call these functions collectors

This makes sense with flux iteration model, but is unwieldy in practice:

// common part
struct IndexedData
{
    int index;
    int data; // assume this is important
};

IndexedData data[] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}};

auto cur = flux::find_if(data, [](auto&& d) { return d.data == 3; });
if (flux::is_last(data, cur)) {
    return -1;
}

auto elem = flux::read_at(data, cur);
return process(elem);

What I would like instead is having an optional:

return flux::read_find_if(data, [](auto&& d) { return d.data == 3; })
        .map(process)
        .value_or(-1);

Proposed solution

I can see two ways to implement this. The tricky part is that flux::read_at needs both the sequence and the cursor, so we cannot simply do .find_if(...).read(). Adding a reference to the sequence in the cursor would be counter productive.

read_*

This solution is the most obvious. For each collector, we can provide a read_ version that will convert the cursor to an optional. We could also make the default collectors (find_if, ...) return an optional, and create cursor_* versions instead

read_self (tap?)

Another solution would be to provide a generic read_self collector. It could be used like this:

return flux::read_self(data, [](auto &&seq) { return seq.find_if([](auto &&d) { return d.data == 3; }); })
        .map(process)
        .value_or(-1)

It would mean less implementation effort, but I'm not sure it would be really ergonomic. Also, this might have side effects I did not think about

Something else?

If there is a better way, I'd love to hear about it


Sidenote: while writing this, I've noticed find_if doesn't seem tested?