I was wondering what a more declarative approach is to the simple problem space of doing something between the elements of some iterator. I feel like that is a task that constantly comes up but can’t be solved with just
zip and friends, because the length of the iterator is of course one more than the iterator of “betweens”.
One example is getting all the midpoints of a range of values. Or checking that some condition holds between subsequent values in a vector. Or intertwining a transformation of the iterator with some values in between. One good example that this is not completely trivial is the implementation of
function join(io::IO, iterator, delim, last) first = true local prev for item in iterator if @isdefined prev first ? (first = false) : print(io, delim) print(io, prev) end prev = item end if @isdefined prev first || print(io, last) print(io, prev) end nothing end
You can see here that the quite rarely seen
@isdefined macro is used to work around the off-by-one annoyance, that no
prev exists on the first run. Generally I always see solutions like checking “am I still in the first run?” or “am I in the last run, yet?” leading to frequent bugs and hard-to-read code.
I would like to see nice generic higher-order functions that can be used to implement
midpoints and other things like that, both eager and collecting like
map or lazy like
And these functions should work with stateful iterators where you can’t get the same element twice and can’t index, because one common solution is
map(f, @view(vec[1:end-1]), @view(vec[2:end])) and I don’t like that one either.
Maybe all my use cases can be thought of in terms of reducers, but I’m not quite clear how these should be set up to be generic and performant.