www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Should we add drop to Phobos?

reply Jonathan M Davis <jmdavisProg gmx.com> writes:
Okay. Right now, if you want to remove n elements from the front of a range, 
you use popFrontN.

popFrontN(range, n);

This is obviously a highly useful function. However, it does not work very 
well when chaining range-based functions. For instance, what if you need to 
call find on a range, but you know that what you're looking for is not in the 
first n elements of that range? You'd do

popFrontN(range, n);
auto found = find(range, needle);

And what if you don't want to alter the range when you did that? You'd do 
something like

auto toFind = range.save;
toFind.popFrontN(range, n);
auto found = find(toFind, needle);

That's not very functional at all and requires multiple lines of code. Also, 
if you want to chain more than one function and need to call popFrontN 
multiple times in there, you can't chain them very well. popFrontN returns the 
actual number of elements popped, not the range, so you can't just pass its 
result into another range-based function.

However, if you had a drop function, you could do this

auto found = find(drop(range, n), needle);

You now only have one line of code instead of three. And if you have more 
complicated chaining, it's that much more useful. However, it does have one 
downside.

Both popFrontN and drop pop _up to_ n elements, so if there are fewer than n 
elements in the range, then as many elements as are in the range are popped. 
However, in the case of popFrontN, it returns the actual number of elements 
popped, so you know how many elements were popped. drop, on the other hand, 
returns the range with the elements popped, so you don't know how many 
elements were popped unless you do something like

auto newRange = drop(range, n);
auto elementsDropped = newRange.empty ? walkLength(range) : n;

Also, drop doesn't alter the original range, so if you call it multiple times 
on the same range instead of using popFrontN, then it's inefficient. However, 
it's not like we're getting rid of popFrontN, so if that's an issue, you can 
just call popFrontN instead. It's just up to the progammer to decide which is 
best for what they're doing. In cases where you want to chain range-based 
functions and don't want to alter the original range, drop is best. In cases 
where you want to alter the original range and don't necessarily care about 
chaining functions, popFrontN is best.

Now, as Andrei has pointed out, you can get a similar effect by doing 
(popFrontN(range, n), range). So, instead of

auto found = find(drop(range, n), needle);

you can do something like

auto found = find((popFrontN(range, n), range), needle);

However, that involves the comma operator, which most people avoid. It also 
alters the original range instead of returning a new one with n elements 
popped off, though whether that's good or bad depends on what you're doing.

So, the question is: Is drop worth having in Phobos? It's common in functional 
languages, and if you want to program in a more functional style, it's of 
enormous benefit. We'll still have popFrontN regardless, so the question is 
whether drop adds enough value to be worth having or whether it's just too 
similar to popFrontN to be worth adding to the standard library.

Personally, I think that drop would be a _very_ useful function to have. What 
do you think?

- Jonathan M Davis
Aug 13 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 08:02 AM, Jonathan M Davis wrote:
 Okay. Right now, if you want to remove n elements from the front of a range,
 you use popFrontN.

 popFrontN(range, n);

 This is obviously a highly useful function. However, it does not work very
 well when chaining range-based functions. For instance, what if you need to
 call find on a range, but you know that what you're looking for is not in the
 first n elements of that range? You'd do

 popFrontN(range, n);
 auto found = find(range, needle);

 And what if you don't want to alter the range when you did that? You'd do
 something like

 auto toFind = range.save;
 toFind.popFrontN(range, n);
 auto found = find(toFind, needle);

 That's not very functional at all and requires multiple lines of code. Also,
 if you want to chain more than one function and need to call popFrontN
 multiple times in there, you can't chain them very well. popFrontN returns the
 actual number of elements popped, not the range, so you can't just pass its
 result into another range-based function.

 However, if you had a drop function, you could do this

 auto found = find(drop(range, n), needle);

 You now only have one line of code instead of three. And if you have more
 complicated chaining, it's that much more useful. However, it does have one
 downside.

 Both popFrontN and drop pop _up to_ n elements, so if there are fewer than n
 elements in the range, then as many elements as are in the range are popped.
 However, in the case of popFrontN, it returns the actual number of elements
 popped, so you know how many elements were popped. drop, on the other hand,
 returns the range with the elements popped, so you don't know how many
 elements were popped unless you do something like

 auto newRange = drop(range, n);
 auto elementsDropped = newRange.empty ? walkLength(range) : n;

 Also, drop doesn't alter the original range, so if you call it multiple times
 on the same range instead of using popFrontN, then it's inefficient. However,
 it's not like we're getting rid of popFrontN, so if that's an issue, you can
 just call popFrontN instead. It's just up to the progammer to decide which is
 best for what they're doing. In cases where you want to chain range-based
 functions and don't want to alter the original range, drop is best. In cases
 where you want to alter the original range and don't necessarily care about
 chaining functions, popFrontN is best.

 Now, as Andrei has pointed out, you can get a similar effect by doing
 (popFrontN(range, n), range). So, instead of

 auto found = find(drop(range, n), needle);

 you can do something like

 auto found = find((popFrontN(range, n), range), needle);

 However, that involves the comma operator, which most people avoid. It also
 alters the original range instead of returning a new one with n elements
 popped off, though whether that's good or bad depends on what you're doing.

 So, the question is: Is drop worth having in Phobos? It's common in functional
 languages, and if you want to program in a more functional style, it's of
 enormous benefit. We'll still have popFrontN regardless, so the question is
 whether drop adds enough value to be worth having or whether it's just too
 similar to popFrontN to be worth adding to the standard library.

 Personally, I think that drop would be a _very_ useful function to have. What
 do you think?

 - Jonathan M Davis
+1. Having take but not drop does not add to the beauty of the module. I'm also strongly in favor of adding dropWhile, dropUntil and takeWhile, plus renaming std.algorithm.until to takeUntil. "until" is an undescriptive name and it has a different meaning in functional programming language libraries.
Aug 14 2011
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, August 14, 2011 13:09:59 Timon Gehr wrote:
 +1. Having take but not drop does not add to the beauty of the module.
 I'm also strongly in favor of adding dropWhile, dropUntil and takeWhile,
 plus renaming std.algorithm.until to takeUntil. "until" is an
 undescriptive name and it has a different meaning in functional
 programming language libraries.
Renaming until takeUntil might be a good idea, though you'd have to talk Andrei into it. However, dropWhile isn't going to make it in because it's the same as find except that predicate is reversed. All you have to do is use std.function.not on your predicate to find, and you have dropWhile. The same goes for takeWhile, except that it's std.algorithm.until which does the same thing aside from the reversed predicate. And dropUntil _is_ find, no reversing of predicates required. There was a fair bit of discussion about this in https://github.com/D-Programming-Language/phobos/pull/147 While I rather like the idea of having takeWhile and dropWhile, Andrei's stance is that because they do the same thing as existing functions except for the reversed predicate, they shouldn't be added. And while I'm not entirely happy with that, I think that he has a very good point. We don't want to clutter Phobos with functions which do the same things as other functions. So, I could see an argument for renaming until to takeUntil. I don't know if Andrei would go for that or not, but that could be reasonble. I certainly agree that the name would be better. However, dropWhile, dropUntil, and takeWhile aren't going to make it in because we already have functions which already do the same thing with at most requiring that you reverse the predicate. drop, on the other hand, I'd argue adds much more value. You can't do drop with any other function in Phobos. The closest that you get is Andrei's suggestion of (popFrontN(range, n), range) which while clever, is fairly hideous IMHO given that it uses the comma operator. So, drop adds signicant benefit towards a more functional style of programming, whereas the others just make it so that you don't have to mess with your predicates as much to do what you want - which would be nice but not nice enough to clutter the standard library with. - Jonathan M Davis
Aug 14 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 01:29 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 13:09:59 Timon Gehr wrote:
 +1. Having take but not drop does not add to the beauty of the module.
 I'm also strongly in favor of adding dropWhile, dropUntil and takeWhile,
 plus renaming std.algorithm.until to takeUntil. "until" is an
 undescriptive name and it has a different meaning in functional
 programming language libraries.
Renaming until takeUntil might be a good idea, though you'd have to talk Andrei into it. However, dropWhile isn't going to make it in because it's the same as find except that predicate is reversed. All you have to do is use std.function.not on your predicate to find, and you have dropWhile. The same goes for takeWhile, except that it's std.algorithm.until which does the same thing aside from the reversed predicate. And dropUntil _is_ find, no reversing of predicates required.
I'd argue that find should actually have been called dropUntil. It is not evident at all what find does, given the name alone. If it is called dropUntil, nobody ever needs to read the documentation, be it the guy who writes the code or the guy who has to read it. But as it is probably heavily used, it is not sensible renaming it now. (and all I have to do is define an alias :))
 There was a fair bit of discussion about this in

 https://github.com/D-Programming-Language/phobos/pull/147
Ok, thanks. However I don't agree that the functions would clutter the standard library: In functional code, it is *crucial* for the understandability and ease of writing of code that it is not necessary to mess with predicates and it is possible to use a natural nomenclature. As things are now, if somebody wants to write functional-style D code, they have to implement a lot of the basic stuff themselves. The discussion is basically between keeping it small and orthogonal (with imho questionable defaults) or making it large, flexible and convenient, without requiring the user to implement many small helper functions. I'm fine with defining my own utility functions, but I think if many people end up writing that functionality themselves, the std library has missed opportunities.
 While I rather like the idea of having takeWhile and dropWhile, Andrei's
 stance is that because they do the same thing as existing functions except for
 the reversed predicate, they shouldn't be added. And while I'm not entirely
 happy with that, I think that he has a very good point. We don't want to
 clutter Phobos with functions which do the same things as other functions.

 So, I could see an argument for renaming until to takeUntil. I don't know if
 Andrei would go for that or not, but that could be reasonble. I certainly
 agree that the name would be better. However, dropWhile, dropUntil, and
 takeWhile aren't going to make it in because we already have functions which
 already do the same thing with at most requiring that you reverse the
 predicate.

 drop, on the other hand, I'd argue adds much more value. You can't do drop
 with any other function in Phobos. The closest that you get is Andrei's
 suggestion of (popFrontN(range, n), range) which while clever, is fairly
 hideous IMHO given that it uses the comma operator.
closer: ((int n){auto s=range.save(); popFrontN(s,n); return s}(n))
 So, drop adds signicant
 benefit towards a more functional style of programming, whereas the others just
 make it so that you don't have to mess with your predicates as much to do what
 you want - which would be nice but not nice enough to clutter the standard
 library with.

 - Jonathan M Davis
Yes, drop should be added. But I think that using the comma operator is no more hideous than using something like: until!(not!p1)(find!p2(range)); instead of takeWhile!p1(dropUntil!p2)(range));
Aug 14 2011
parent "Marco Leise" <Marco.Leise gmx.de> writes:
Am 14.08.2011, 14:45 Uhr, schrieb Timon Gehr <timon.gehr gmx.ch>:

 I'd argue that find should actually have been called dropUntil. It is  
 not evident at all what find does, given the name alone. If it is called  
 dropUntil, nobody ever needs to read the documentation, be it the guy  
 who writes the code or the guy who has to read it. But as it is probably  
 heavily used, it is not sensible renaming it now. (and all I have to do  
 is define an alias :))
When I first came across find I expected it to find a single item. dropUntil makes more sense to me, too.
Aug 14 2011