-
-
Notifications
You must be signed in to change notification settings - Fork 747
Fix Issue 9682 - group/filter should return a SortedRange when they are passed one #5712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ge) ==> SortedRange
|
Thanks for your pull request, @RazvanN7! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. Bugzilla references
|
|
The most apparent problem with this is why stop at these two functions? Why not add this special case to every function which returns it's own range type? I'd rather leave these sorts of special cases up to the user. Especially since this is one of the reasons |
|
The other problem with this type of code is that code like this auto r = range_thats_sorted.assumeSorted;
auto b = r.func().func().func().assumeSorted;vs the following with your changes auto r = range_thats_sorted.assumeSorted;
auto b = r.func().func().func();is that b in the first version will have a type of and in your code it will be |
I think that most functions should preserve the type range that is passed as parameter, although I agree that adding tests for each type of range is not the way to go.
Yes, that bothers me too and I tried to search for a way to get the underlying range of SortedRange (without using release()) so that only Group/FilterResult will be a SortedRange; I even asked about this on Slack), but couldn't find any solution, so I thought I'd just make a PR and see what other folks think about this. Should be close this and bug report? |
In practice I think this is impossible because you need to "overload"
Yes, I would close that as won't fix. I believe this is one of those things that the user has to be in charge of. Thanks for all of your work. |
|
@JackStouffer Propagating range sortedness is one of the biggest algorithmic optimization that we can do in Phobos, so I strongly disagree that this is not an area worth pursuing. This in an area that D can do much better than other languages and we should try to pursue it too the full extend possible. |
|
On the other hand, I'm also not 100% sold on the idea of adding Probably an approach like we do for infinite ranges would worth pursuing. auto someRangeAlgo(R)(R range)
{
struct Result
{
R _range;
static if (isStaticallyKnownToBeSorted!R)
{
enum isSorted = true;
alias sortPredicate = getSortPredicate!R;
// If applicable
mixin SortedRangeAlgos!_range;
}
// front, popFront, empty, ...
}
return Result(range);
}(Of course the whole |
|
@ZombineDev Been thinking about this some more. I think you're right in that there is value in propagating sortedness when there's a lot of room in Phobos for more sortedness optimizations. I'll reopen this for further discussions. I'm still not really sold on the idea of doing things automatically, even if less experienced users won't get all of the benefits. One other problem that any solution would need to address is non-pure predicates/lambdas in things like
It's not just the names, you also balloon the size of the end result struct. |
| return FilterResult!(unaryFun!predicate, Range)(range); | ||
| import std.range : SortedRange, assumeSorted; | ||
| static if (is(Range : SortedRange!TT, TT)) | ||
| return assumeSorted(FilterResult!(unaryFun!predicate, Range)(range)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sorting predicate should be propagated here, e.g. return FilterResult!(unaryFun!predicate, Range)(range).assumeSorted!TT;
| static if (is(Range : SortedRange!TT, TT)) | ||
| return assumeSorted(FilterResult!(unaryFun!predicate, Range)(range)); | ||
| else | ||
| return FilterResult!(unaryFun!predicate, Range)(range); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code is repetitive here, should be something like:
auto result = FilterResult!(unaryFun!predicate, Range)(range);
static if (is(Range : SortedRange!order, order))
return result.assumeSorted!order;
else
return result;| return typeof(return)(r); | ||
| import std.range : SortedRange, assumeSorted; | ||
| static if (is(Range : SortedRange!TT, TT)) | ||
| return assumeSorted(Group!(pred, Range)(r)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
|
@ZombineDev @JackStouffer there are several current and future optional features of ranges. We currently fully support Nevertheless, there is value in investigating the cost/value of propagating sortedness. @JackStouffer could you please make two lists - one with algorithms/ranges that preserve sortedness, and one with those that don't? Then we should have a good image of what it takes to implement this policy across Phobos. Thanks! |
|
I'm away from my laptop ATM, I'll take a look on Monday |
|
Reflecting on this a bit more, I did not understand the full implications of @ZombineDev's comment. In the chain auto r = knownSorted.assumeSorted();
r.filter!(func).find!("a == thing");It's not obvious that The user not knowing/caring about which functions do or do not take advantage of sorted information does present a problem of knowing when and when not to use assume sorted. However, the above problems still stand. |
|
We should take into account the fact that sometimes the range may be sorted with a predicate and the function is called with a different predicate. For example: [5, 4, 2].assumeSorted!"a > b".find!"a < b"(4). In most case you probably can safely propagate sortedness only when the predicates match (which is tricky because you need to do function comparison). |
|
Currently it's not possible to compare predicates in the general case. For example, One way it might be able to be done is to add |
|
There are two predicates involved in |
I was trying to highlight the fact that in order to benefit from sortedness or propagate it we need to do predicate comparison. There is one case in which find uses the sortedness to its advantage and that is only when the sort predicate is the default one [1]; this in my opinion is a weak implementation (it is politically correct to say that, since it is mine) - a powerful one should take into account the different combinations of find-sort predicates and this point is valid for all the functions in phobos which may benefit from. or propagate sortedness. [1] #4907 |
What if, instead of generating a new lambda every time, we make the compiler generate a named function for the given predicate and argument types and memoize it? Now, when it encounters a predicate, we first check the lookup table for the current predicate and argument types and we either point to the existing (previously generated function) or create a new one. Just a thought, though I don't know if this covers all the cases, or how hard it would be to implement. |
|
@edi33416 The problem with functions is that it is difficult to compare them for equality. How do you know for sure that f(x) is equal to g(x)? For simple predicates like "a < b" it is simple, but for more complex functions there is no trivial solution. |
|
@edi33416 unfortunately it's not so simple. Any possible solution will probably be very complicated and full of special cases if we allow impure functions to be compared. Not only do you have to know about the function, but the context that it might be carrying with it. Maybe normalizing variable names to de Brujin indices can help in most cases but I think it'll be very hard to have a 100% solution. It's impossible in the general case because ultimately, I think checking two lambdas for equality reduces to the halting problem (f == g iff forall n f(n) == g(n), which would require running the functions, but we don't know if they'll terminate). |
|
@andralex Doing a quick scan of the docs for std.algorithm and std.range, here's what I've come up with. Functions which preserve sortedness w/
|
|
There is another method of checking whether two functions are equal that I didn't consider, and it doesn't reduce to the halting problem (I think). If two functions generate the same code (variable names aside), we should consider them equal. This is a different notion of function equality (it cares about how they're implemented rather than what they produce) and I don't know how practical it is, but it may be worth thinking about as well. |
|
@MetaLang this has been discussed before (I think in the forum). Two functions can be comparable by means of the unification algorithm https://en.wikipedia.org/wiki/Unification_(computer_science). The implementation should allow alpha renaming http://wiki.c2.com/?AlphaEquivalence. Could you please add a bugzilla? Thanks! |
|
Will not pursue the strategy of propagating sortedness in phobos. |
No description provided.