This is my first time trying to write out a proposal so bare with me. I think generators are an amazing tooll for generating sequences that would typically end-up needing a heck of a lot of for loops and other such code to benefit from the re-usability + lazy revaluation they offer.
But in my experience I find there are two cases where they fall short.
- It’s easy to end up with a rats nest i.e
tqdm(enumerate(windowed(brackets, 2)))
- Or can un-nest it but I’ve had many bugs caused by the order of lines being mixed up or people forgetting they need to convert to a list because what they are using is a generator because the list is stored in a variable with another name
I would love to keep the compactness and colocatedness of option 1 with the readability of 2.
Therefore my proposal is to introduce a funnel operator, it would accept an iterable on the left-hand side, a generator factory on the right and return an iterator of it’s own. Each invocation would be evaluated from left to right.
So take this for example
[message1, message2, messge 3] |> filter(lambda message: not message.deleted) |> map(lambda message: translate(message, 'french')
The list would be passed to the filter generator factory and return a generator which would then be passed to the map generator factories. Generator factories are just functions which accept an iterator and then return a generator which consumes that iterable.
So we end up with this kind of nice succinct code from my example ealier
for index, bracket in brackets |> windowed(2) |> enumerate() |> tqdm():
# Code here
pass
So why generator factories at all? At first I thought we could use some kind of system of currying the iterable to the existing generators, but unfortunetly the existing ones in itertool and python often have inconsistent interfaces where sometimes the iterable needs the be the first, second or N argument.
Therefore we need a factory function which handles the details of how to construct these generators. This also may allow for backwards compatibility for example if map
were to become a factory since the function could perhaps handle cases where it’s called expecting to be a generator vs a factory.
But yeah I’m not really sure how you would pull this off regarding backwards compatability.