-
-
Notifications
You must be signed in to change notification settings - Fork 741
fix Issue 12409 - Add "each" function as found in Ruby and jQuery #2024
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
unittest | ||
{ | ||
int[] arr; | ||
iota(5).each!(n => arr ~= n); |
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.
vs
foreach (n; iota(5)) { arr ~= n; }
About equivalent, though it's nice that the range comes first if it's a long pipeline.
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.
Yep, that's the point :) See the example in the DDoc and the discussion in the Bugzilla issue.
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.
About equivalent, though it's nice that the range comes first if it's a long pipeline.
While fine as a test, this shouldn't be taken as an example of anything. The better way to do this would be:
auto arr = appender!(int[])();
iota(5).copy(arr);
I've always been against this. We have a fairly nice foreach statement that does exactly the same thing but in We also shouldn't take after Ruby on this; Ruby inherits the Perl way of providing a ton of ways to express the same thing, which I think is anti-thetical to the D approach. edit: Sorry, s/declarative/imperative. The functional code is declarative but ceases to be so when imperative code is injected into the chain. |
That's a rather fundamental look at the issue. But I don't think it holds up. We already have functions which eagerly evaluate its first range parameters, such as
I don't see why you think so. D is a multi-paradigm language, so by definition it provides multiple ways to accomplish the same thing - in different paradigms. The same goes for UFCS. It would not have existed with that logic: it provides two ways to do the same thing, and it allows mixing programming styles. Yet it thrives as one of D's most important features. |
I have less reservations about this than for "tee" in regards to the whole imperative code in functional style: "each" is a tail call, and the iteration scheme is clearly defined. It does not produce a range. That said, I'm starting to wonder if there might not be a more "generic" way of achieving this: "tee" is still under review, and if it passes, then "each" could be replaced by a So my stance on |
|
I think a good analysis of There must be others. Let's collect a list. Oh, and also: @CyberShadow you should define |
Not sure why "better". They are different.
That's a good point. Would |
One more thought: if |
(github ain't that good at Unicode eh) |
@monarchdodra depends on the default lambda of |
Must be the code page your computer is using, and not github/unicode. "Raison d'être". 日本語. |
If |
auto cutoff = Clock.currTime() - 7.days; | ||
dirEntries("", "*~", SpanMode.depth) | ||
.filter!(de => de.timeLastModified < cutoff) | ||
.each!remove(); |
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.
It's nice you can call remove
in this point-free style, without needing a variable.
If the function passed to each returned one of enum ControlFlow { _break, _continue } you could break out of each(). If it has a different return type nothing happens. If you really want that functionality in each, but I think it is over-engineering. |
Or just use |
Yah I think not being able to break is a feature, not an insufficiency of |
Algorithms that eagerly consume a range are perfectly fine, and
The problem is with interfaces that don't differ in anything but style, i.e. aliases. Ruby's |
I think we could and should judge this on its own merit rather than drawing Ruby comparisons etc. |
{ | ||
unaryFun!fun(range.front); | ||
range.popFront(); | ||
} |
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.
Seems to me this loop could be implemented as:
foreach (e; range)
unaryFun!(e);
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.
Existing form is a tad better because it doesn't create a copy in certain cases.
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.
foreach (ref e; range)
unaryFun!(e);
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.
Huh, that works. ref
is auto ref
when it is in a foreach
?
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.
It won't work properly with ranges of which front
returns rvalues. Just let it be.
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.
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.
@yebblies: noice
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.
These two issues may as well get each
over the hump!
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.
That won't work for narrow strings.
foreach (ref ElementType!Range e; range)
unaryFun!(e);
This is the approach used in count
:
https://github.com/D-Programming-Language/phobos/blob/9404dd477fd59925acd958cb0cf21711890420d7/std/algorithm.d#L6663
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 fact that
ref
in foreach binds to rvalues is a bug in the design of D.
All code that does that is wrong. We should break it and replace it with an auto ref
feature instead.
Existing form is a tad better because it doesn't create a copy in certain cases.
It also doesn't create a copy of range
when it's a ForwardRange
, though it seems here that might actually be desired (not currently done).
I'm in favor of adding this. |
I'm getting convinced this is useful/nontrivial. There are a few things that should be added:
|
For what it's worth, just make Note that the "consistent behavior" is important (iMO), or you'd get a different behavior from
Not sure what you are asking for. Could you elaborate? |
I guess.
Enumerate, like iota(1, 3).each!((i, n) => writeln(i, ":", n, " ")); // 0:1 1:2 |
I was actually looking for this function in Phobos just yesterday, and was surprised to not find it. As a side thought, I imagine that the call to |
(Note I'm still fundamentally opposed to this function, especially if the template parameter is optional.) |
Sorry to keep everyone waiting. Catching up now. TODODesign:
Implementation:
|
I think |
|
I'm still in favor of adding this. Who makes the decision on merging or rejecting? |
Dunno... generally we committers merge when there is consensus, but @Dicebot seems to be in disagreement on this PR and there hasn't been any arguments for merging since then. So I'm holding off on deciding on anything just yet. You might want to ping the other reviewers/committers. |
One thing to note is that a |
with e.g. $(XREF parallelism, parallel). | ||
|
||
See_Also: $(XREF range,tee) | ||
|
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.
Missing Params:
and Returns:
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 description would make them redundant, no?
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.
If the description is redundant, remove the description not the blocks.
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.
I don't see a way to word it in that way without also making it unnecessarily awkward.
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 function returns void
. Why do you want me to add a Returns:
block for a void function?
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.
Sorry, didn't realize it returned void. I agree there should not be a Returns:
block for void returns.
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 Params:
block should be:
Params:
fun = predicate to apply to each element of the range
range = range over which each iterates
Note that the original description describes a $(D r)
parameter but there is no parameter r
. The reason for a Params:
section is because inconsistencies like this regularly and repeatedly recur.
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.
And what if the parameter name varies depending on which overload you call? Should the parameter names be homogenized for the sake of Params:
?
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.
I think homogenizing them should be the best approach.
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.
Agreed. The more structure the better we can organize documents and the more readable they'll be to users.
Anyone know why there's now 2 results for the autotester?
|
} | ||
} | ||
|
||
void each(Iterator)(Iterator iterator) |
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.
This is calling something an iterator when it is actually a range. Please call it a range. I'm also baffled by the idea introduced here that something can be a range and not be iterable? Why is there a whole new range (!) of terms being introduced?
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.
No. In this case, it is actually an iterator. This overload handles objects which define opApply
, not ranges.
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.
Please call it something else. It has nothing in common with C++ iterators. The documentation on opApply never calls them iterators, it calls them apply
functions.
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.
I wasn't thinking of C++ iterators when I chose the term, but more of those found in Python/Ruby/C#. What term would you suggest, then? (If not for the parameter name, then for the inferred type name).
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.
How about apply function?
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.
That's a subset of accepted parameters. It wouldn't be the right term for classes/structs which declare opApply
, right?
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.
Call it Iterable
? That's the term used in phobos.
Regarding documentation, there is great advantage to using 'Params:', 'Returns:', 'Throws:' etc uniformly because they can be styled and handled in various ways when generating documentation. More structure can help a lot. (I do agree a function without 'Returns:' can be assumed to return |
Implemented suggested changes and rebased. |
LGTM. Always wanted this function. @andralex ready to merge? |
{ | ||
while (!r.empty) | ||
{ | ||
cast(void)unaryFun!pred(r.front); |
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.
why the cast?
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.
To silence the warning that we are throwing away the return value. (x4)
I think this code sans the casts is good to go. Thanks! |
Auto-merge toggled on |
Time to get this rolling methinks! |
(I'm really excited for the next release, we've gotten so many new features recently in Phobos) |
fix Issue 12409 - Add "each" function as found in Ruby and jQuery
Somewhat related I now wonder about the soundness of import std.algorithm, std.range, std.stdio;
void main(string[] args)
{
long[] arr;
const n = 3;
iota(n).map!(a => arr ~= a);
writeln(arr);
writeln(iota(n).map!(a => arr ~= a));
writeln(arr);
} that prints []
[[0], [0, 1], [0, 1, 2]]
[0, 1, 2] Shouldn't a warning at least be issued for return-ignoring calls to map with mutating lambdas? Has there been any discussions on making map require pure functions now that we have each? I guess a new function, say BTW: Should I turn this into a discussion on the forums instead? |
I have reopened the issue with some comments: https://issues.dlang.org/show_bug.cgi?id=12409 |
Nice work! |
@nordlow forum would probably be best |
https://d.puremagic.com/issues/show_bug.cgi?id=12409