Skip to content

Fix Issue 6787 - Implement std.algorithm.lazySort #1886

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

Closed
wants to merge 2 commits into from

Conversation

Poita
Copy link
Contributor

@Poita Poita commented Jan 26, 2014

@DmitryOlshansky
Copy link
Member

LGTM

_heap.removeFront();
}
}
return Result(r);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could save some code by passing heapify!more(store) here instead of using a constructor... but I guess it's partially a stylistic thing, as sometimes a constructor is strictly required for its special "cooking" privileges.

@quickfur
Copy link
Member

Should the struct be a private module-level struct instead of a voldemort? Last I heard, we have problems with voldemorts when alias parameters are involved.

if (isRandomAccessRange!Range &&
hasAssignableElements!Range &&
!isInfinite!Range &&
is(typeof(binaryFun!less(r.front, r.front)) : bool))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the is(typeof(...) : bool) is a non-pattern. The only thing it buys us, is turning down perfectly valid predicates. I don't have an example a sorting operator, but for a "test" operator, canFind returns an int...

@monarchdodra
Copy link
Collaborator

Should the struct be a private module-level struct instead of a voldemort? Last I heard, we have problems with voldemorts when alias parameters are involved.

Yes, if the voldemort internal struct is declared static, then it will fail to compile if the passed predicate requires a context. Any "ResultType" that depends on a predicate (or more generally, and alias parameter), must not be voldemort. Also (I seem to remember), that if we declare it as non-static, there may be some odd behavior if you do things like assignments, or save. Long story short: global private is better suited for several reasons.

- Using documented unit test
- De-Voldemortification
- Removed bool check
- Fixed import order
- Duplicate element unit test
@JakobOvrum
Copy link
Contributor

Long story short: global private is better suited for several reasons.

I think it's terrible that we have to make these fundamental style decisions based on DMD bugs...

@braddr
Copy link
Member

braddr commented Jan 29, 2014

Well, strictly speaking you don't. The code is there for anyone sufficiently motivated to address the issue(s). There's a number of helpful contributors you could talk to about the specific issues and they might be willing to help with the fix or to even do it themselves. Taking the work around approach is just one option, not the only one, and not even the best one.

@monarchdodra
Copy link
Collaborator

Well, strictly speaking you don't. The code is there for anyone sufficiently motivated to address the issue(s).

Yeah, but fundamentally, I think there is a design issue in there too.

That said, a workaround (I just thought of is):
The main issue is that the Result needs context to the fun encompassing function for context to "get" pred. however, if you actually make Result a template struct that depends on pred, then you can still make it static. In this case, instead of needing context to fun to "get" pred, it has context to pred directly.

auto fun(alias pred)()
{
    static struct Result(alias pred) //Look! I'm static!
    {
        bool foo()
        {
            return pred(5); //No problem!
        }
    }
    return Result!pred();
}

void main()
{
    int k;
    auto s = fun!(a=>a==k)(); //This pred needs context.
}

I think this creates 0 overhead too. I just tested this with map, and it seems to not break anything: map was one of the first Voldemort types to be subject to these bugs.

@monarchdodra
Copy link
Collaborator

One last thing: For what it's worth, as an end user, I personally dislike working with voldemort return types. When working with them, it seems like you always end up having to deal with Result!(Result!Result, Result) types... which is quite difficult to deal with when you have a long chain and a compile error.

@quickfur
Copy link
Member

On Wed, Jan 29, 2014 at 10:10:27AM -0800, monarch dodra wrote:

One last thing: For what it's worth, as an end user, I personally
dislike working with voldemort return types. When working with them,
it seems like you always end up having to deal with
Result!(Result!Result, Result) types... which is quite difficult to
deal with when you have a long chain and a compile error.
[...]

This isn't so much a flaw of voldemort types, as a flaw in compiler
error messages. Voldemorts should be qualified with the function name or
name of the enclosing scope instead of just an ambiguous identifier.
Things are much clearer when the error refers to:

chain.Result!(zip.Result!(map!"f(x)".Result), join.Result!R)

than to:

Result!(Result!Result, Result!R)

T

Curiosity kills the cat. Moral: don't be the cat.

@ghost
Copy link

ghost commented Jan 29, 2014

It's not the only reason why Voldemort's are problematic, another reason is that it's difficult to use them as a type for a field in an aggregate.

@Poita
Copy link
Contributor Author

Poita commented Jan 29, 2014

I think this is getting off topic.

Are there any other issues with this pull that people would like addressing?

@monarchdodra
Copy link
Collaborator

Are there any other issues with this pull that people would like addressing?

Is the fact that lazySort does a heap sort an "implementation detail", or something that can be counted on? Also, I think it should better document that this range is "destructive" of the original range: Eg: it doesn't just iterate in ascending order, it does a gradual sort.

if we want to document that lazySort does a heap sort, then we should suggest running a reverse afterwards to "finish" sorting the original range.

@Poita
Copy link
Contributor Author

Poita commented Jan 29, 2014

I don't think it should be documented to do a heap sort. That would lock us into a specific implementation that we may regret later.

Edit: I will improve the documentation to make it clear that the range is modified, but I will not specify how it is modified since that could again lock us into a specific implementation.

Actually, I think it is pretty well documented as is. The second sentence of the documentation is "$(D r) is modified by the range in-place." Is there a different phrasing you'd rather it used?

@monarchdodra
Copy link
Collaborator

It's not the only reason why Voldemort's are problematic, another reason is that it's difficult to use them as a type for a field in an aggregate.

That's a different issue (IMO), since most of the time, the return type is deliberately kept unspecified.

@andralex
Copy link
Member

This is a thin wrapper over the heap. Necessary?

@DmitryOlshansky
Copy link
Member

@andralex

This is a thin wrapper over the heap. Necessary?

Now that I think of it - a range over heap (as container) would make this stuff obsolete.
How about adding opSlice for BinaryHeap and an example to address the enhancement in bugzilla ?

@monarchdodra
Copy link
Collaborator

Now that I think of it - a range over heap (as container) would make this stuff obsolete.

I think it'd make even more sense to simply give heap a range interface. It is already a wrapper over something else (not an actual container itself), so I see no problem doing that. It would have more primitives than your basic range, but that shouldn't keep it from being a range itself (I think).

That said, I'm not completely familiar with Heap, so I could be wrong.

@Poita
Copy link
Contributor Author

Poita commented Jan 30, 2014

That makes sense. By the looks of things, we'd just need to add alias popFront = removeFront; and we'd have a range interface for BinaryHeap.

@andralex would you be happy with that?

@JakobOvrum
Copy link
Contributor

That makes sense. By the looks of things, we'd just need to add alias popFront = removeFront; and we'd have a range interface for BinaryHeap.

Even if the range must be destructive, we should probably stick to opSlice, for least surprise.

@monarchdodra
Copy link
Collaborator

Even if the range must be destructive, we should probably stick to opSlice, for least surprise.

Keep in mind Head is not forward, and does not provide save. I think providing opSlice would create more surprise, since it implies a save. Yet if you pop the sliced range, the underlying Heap will have been modified! (And possibly corrupted to boot... it will have inconsistent length).

I'm not even sure an opSlice could work?

@JakobOvrum
Copy link
Contributor

Keep in mind Head is not forward, and does not provide save. I think providing opSlice would create more surprise, since it implies a save. Yet if you pop the sliced range, the underlying Heap will have been modified! (And possibly corrupted to boot... it will have inconsistent length).

Yeah, sorry; I should have familiarized myself with BinaryHeap before giving an opinion.

@monarchdodra
Copy link
Collaborator

@andralex would you be happy with that?

I'd be happy with that.

@DmitryOlshansky
Copy link
Member

I concur with @monarchdodra on this one, let's make BinaryHeap a range and close the bugzilla of lazySort.

@Poita
Copy link
Contributor Author

Poita commented Mar 1, 2014

I'm not so sure about this. BinaryHeap is meant to be a container, not a range. It feels wrong to be able to destructively iterate a container.

Perhaps this could be a more general facility to consume any range?

struct Consume(Container)
{
    Container _container;
    @property auto ref front() { return _container.front; }
    @property bool empty() { return _container.empty; }
    void popFront() { _container.removeFront(); }
    // ... could add back, indexing, length etc. not saving, not slicing
}
auto consume(Container)(Container c) { return Consume!Container(c); }

This way, you can consume a BinaryHeap to lazily iterate a container destructively. You could also consume and iterate any other container (as removeFront is an axiom of containers).

Thoughts?

@DmitryOlshansky
Copy link
Member

Perhaps this could be a more general facility to consume any range?

You mean container. It is interesting. I assume with combination with things like take it could be very versatile. I can where front/back goes but there is also removeAny any ideas on extending it otherwise then potentially bidirectional range?

@monarchdodra
Copy link
Collaborator

I'm not so sure about this. BinaryHeap is meant to be a container, not a range.

According to doc, it's not a container either, it's a mere wrapper. It can wrap either a range, or a container, in which case, it also forwards the container primitives.

@andralex
Copy link
Member

andralex commented Mar 9, 2014

I think making BinaryHeap a forward range would be awesome.

@monarchdodra
Copy link
Collaborator

I think making BinaryHeap a forward range would be awesome.

AFAIK, InputRange, but not ForwardRange.

@DmitryOlshansky
Copy link
Member

I think making BinaryHeap a forward range would be awesome.

Perfect, @Poita - fire at will :)

@andralex
Copy link
Member

andralex commented Mar 9, 2014

Why wouldn't save work? Oh yes you're right, every step ruins the range for the others. I guess we could make it work by saving the most advanced position etc. but that's onerous.

@Poita
Copy link
Contributor Author

Poita commented Mar 9, 2014

I'll close this pull and make a new one since it's a completely separate approach to the problem.

@Poita Poita closed this Mar 9, 2014
Poita added a commit to Poita/phobos that referenced this pull request Mar 9, 2014
@Poita
Copy link
Contributor Author

Poita commented Mar 9, 2014

#1989

Poita added a commit to Poita/phobos that referenced this pull request Mar 15, 2014
Fixes Issue 12358

See discussion here: dlang#1886
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants