Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion std/range/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ $(BOOKTABLE ,
$(TD Creates a random-access _range consisting of exactly the first
element of the given _range.
))
$(TR $(TD $(LREF takeWhile))
$(TD Creates a sub-_range consisting of the first elements of the
given _range that satisfy a given predicate.
))
$(TR $(TD $(LREF tee))
$(TD Creates a _range that wraps a given _range, forwarding along
its elements while also calling a provided function with each element.
Expand Down Expand Up @@ -235,7 +239,7 @@ public import std.typecons : Flag, Yes, No;
import std.meta; // allSatisfy, staticMap
import std.traits; // CommonType, isCallable, isFloatingPoint, isIntegral,
// isPointer, isSomeFunction, isStaticArray, Unqual

import std.functional : unaryFun;

/**
Iterates a bidirectional range backwards. The original range can be
Expand Down Expand Up @@ -11844,3 +11848,67 @@ pure @safe unittest
static immutable r2 = [1, 2, 3, 4, 0, 0];
assert(r1.padRight(0, 6).equal(r2));
}

private struct TakeWhile(alias pred, R)
if (isInputRange!R && is(typeof(!unaryFun!pred(ElementType!R.init)) == bool))
{
private R _range;

// Range primitives
bool empty() @property
{
return _range.empty || !(unaryFun!pred(_range.front));
}

auto front() @property
{
assert(!empty, "Attempting to fetch the front of an empty TakeWhile range");
return _range.front;
}

auto save() @property
{
return this;
}

void popFront() @property
{
assert(!empty, "Attempting to pop the front of an empty TakeWhile range");
_range.popFront;
}
}

/**
Lazily produce elements of a range as long as the predicate holds
for the produced elements.

Params:
range = the range to take from while the predicate stands
pred = the prediacate used to take the elements

Returns:
A `ForwardRange` which contains the taken elements.

See_Also: $(LREF chain) to chain ranges
*/
auto takeWhile(alias pred, R)(R range) if (isInputRange!R)
Copy link
Contributor

@wilzbach wilzbach Jul 7, 2017

Choose a reason for hiding this comment

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

alias takeWhile = until;

https://dlang.org/phobos/std_algorithm_searching.html#.until

This seems to be especially hard to find even though I tried to help people with #4985

Btw see also: #147

Copy link
Contributor

@wilzbach wilzbach Jul 7, 2017

Choose a reason for hiding this comment

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

Other common confusions:

  • dropWhile -> find
  • flatten -> joiner
  • any -> exists
  • zipMap -> assocArray
  • indexOf vs. (byChar).countUntil
  • group -> chunkBy

Copy link
Contributor

Choose a reason for hiding this comment

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

You might also like this table: https://github.com/wilzbach/linq for a mapping from LINQ to D

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think that takeWhile is much cleaner than until, in the sense that it is more generic and it doesn't have a sentinel. See discussion on bug report

Copy link
Contributor

Choose a reason for hiding this comment

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

I like takeWhile as well, but I'm pretty sure that @andralex will veto this as it adds yet another symbol to std.algorithm
Hence to save you work, my advice is to look into incorporating your changes into until.

Copy link
Member

Choose a reason for hiding this comment

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

IIRC, I tried to add takeWhile a number of years ago, and he vetoed it along with other stuff like dropWhile. Basically, the decision was that we could do what we needed to do with what we had, and you're always going to have problems with folks finding stuff, because they assume one name, whereas the person who designed the function thought of another - and changing the name often just results in a different set of people not finding what they're looking for (a prime example of this would be strip vs trim - some languages/libraries use one and some the other, and both are valid names, but if the library you're trying to use didn't use the one you're thinking of, you might not find it). And we really can't fix that problem. You try to pick a good name and then try and make sure that your documentation makes it easy to find.

In this case, until already does what this PR does - it even has an overload that doesn't take a sentinel. And honestly, the fact that is does have the ability to indicate a sentinel as well as whether it's open on the right makes it more useful than this implementation of takeWhile.

Certainly, I see no reason to add this code. It's just doing what until already does - only less. At most, it would make sense to create an alias or to make takeWhile a wrapper around until, but neither would help functionality at all. It's just bikeshedding over the name, and while it might make it easier for some people to find the function, it's also going to result in confusion over the difference between takeWhile and until. Our policy has generally been to avoid adding aliases to provide additional names.

So, while I think that this is ultimately up to Andrei, I don't think that it makes sense to add takeWhile given that we already have until, much as I agree that takeWhile would likely have been a better name.

Copy link
Member

Choose a reason for hiding this comment

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

If the implementation is really short (one-liner or alias), perhaps a good compromise is to implement it as an example unittest (e.g. as here). This way we 1) make the name findable, 2) show off Phobos' composition capabilities, and 3) avoid cluttering the name space with trivial one-liners. Win-win-win.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got it. I only made the PR so that we move on with the bug (closing it as invalid or fixing it). Thanks

Copy link
Member

Choose a reason for hiding this comment

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

@CyberShadow until's documentation actually already mentions takeWhile, saying

This is similar to `takeWhile` in other languages.

So, as far as searchability goes, if they search the documentation, they'll find it. It just won't be in the links, because it's not actually a symbol, just a comment in the documentation.

{
return TakeWhile!(pred,R)(range);
}

///
@safe unittest
{
import std.algorithm.comparison : equal;

auto arr = [0,1,2,3,4,3,2,1,0];

assert(equal(takeWhile!"a < 3"(arr), [0,1,2])); // standard case
assert(equal(takeWhile!"a < 10"(arr), arr)); // predicate true for all elements
assert(takeWhile!"a < 0"(arr).empty); // predicate false from the beginning
assert(takeWhile!"a != 0"(arr).empty); // predicate false for the first element

// With a standard function
bool foo(int i) { return i != 4;}
assert(equal(takeWhile!foo(arr), [0, 1, 2, 3]));
}