Why does `filterBidirectional` exist, and why isn't it called `filter`?
Steven Schveighoffer
schveiguy at gmail.com
Thu Mar 9 19:43:53 UTC 2023
On 3/9/23 11:44 AM, FeepingCreature wrote:
> On Thursday, 9 March 2023 at 13:16:07 UTC, Steven Schveighoffer wrote:
>> On 3/9/23 3:06 AM, FeepingCreature wrote:
>>> Yes I know the stated reason, to save time initializing the range in
>>> `filter`.
>>>
>>> You know what I did because we didn't know `filterBidirectional`
>>> existed?
>>>
>>> I literally *walked the entire range* returned by `filter` to find
>>> the last matching element.
>>
>> Why not `rng.retro.filter`?
>
> `retro` changes the semantics of the range, which often represents
> business data. Thinking about the underlying model is complicated enough
> without throwing in "But consider: what if the elements were in reverse
> order".
Well, walking the entire range to find the last one doesn't imply that
ordering matters at all. You are just looking for the last one.
>>> `filter` should expose `back`. By all means, let `back` lazily
>>> initialize, but I don't understand how `filterBidirectional` was ever
>>> considered acceptable. Since when do we sacrifice ease of use for
>>> performance? I mean, since 2011 apparently. - This is bad ergonomics,
>>> bad discoverability and bad API design. `filterBidirectional` delenda
>>> est.
>>
>> This has been a constant debate -- should ranges be allowed to lazily
>> initialize on the first call to front (or back)?
>>
>> I'd say no. back/front shouldn't be modifying operations. I dislike
>> the idea of storing a "was initialized" flag and having to check that
>> on every call.
>>
>> That being said, there's no formal requirement for it. It's just a
>> convention I think Phobos should stick to for its included ranges.
>>
>
> Sure: so precompute both `back` and `front`, every `filter` call. No
> caching. This isn't done right now because the language presumes,
> arbitrarily, that you won't care about `back`. That's what I meant by
> sacrificing convenience for performance. Just do what
> `filterBidirectional` does, every time, and have the current `filter` be
> the odd one out. `filterForward` or whatever.
I would be fine with that. But in truth, this is simply an acceptance of
the status quo, just with different names.
> (I think the best way to go, in truth, is to *preinitialize front* and
> *cache back*. But that solution, much as I think it is optimal for all
> involved interests, is very hard to love.)
I prefer to have control over all the capabilities. If there is going to
be significant cost for something I won't actually use, I'd rather avoid it.
Allowing composability might be the way to go.
> That said, I think the range API has a fundamental problem here.
> `filter` pre-initializes `front` on every access, because it reasonably
> presumes that we will then want to query it. However, it cannot rely on
> this. If we were *guaranteed* that every access to `popFront` would
> alternate with at least one access to `front`, and conversely `back`
> with `popBack`, we could just reduce `popBack` to `nextRange.popBack`
> and do the rest of the popping in `back`! Then in the fast case (one
> access to each, in turn) we would not waste any check. And the actual
> `filter` call would always be constant-time.
>
`front` and `empty` are semantically non-mutating properties. Calling
`front` and/or `empty` as many times as you want should not alter any
values until you call `popFront`.
I prefer them to be *actually* non-mutating as well.
Delaying construction seems like the better option, and having such a
wrapper solves all those problems for those who want lazy, without
imposing on users who are intending to just use a range as it's intended.
-Steve
More information about the Digitalmars-d
mailing list