www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Maybe we don't need foreach ;-)

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Just like some people have said, you can do everything with 'for'.
Well, actually maybe you can.  It occurred to me that foreach and for 
really don't clash.  One requires two semicolons, the other only one. 
So instead of a foreach we could have had

   for(a; aggregate)
   {

   }


or if the keyword 'in' had been used for the new construct instead of 
borrowing from c's ; syntax it would be:

   for(a in aggregate)
   {
      ...
   }

which could quite peacefully coexist with good-ole

   for (int i=0; i<10; i++)
   {
   }

Oh well.  Too much momentum behind foreach and foreach_reverse now, I 
suppose, to make such a simplification.

--bb
Oct 30 2006
next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Bill Baxter wrote:
 Just like some people have said, you can do everything with 'for'.
 Well, actually maybe you can.  It occurred to me that foreach and for 
 really don't clash.  One requires two semicolons, the other only one. So 
 instead of a foreach we could have had
 
   for(a; aggregate)
   {
 
   }
That looks like a syntax error for one used to for loops.
 or if the keyword 'in' had been used for the new construct instead of 
 borrowing from c's ; syntax it would be:
 
   for(a in aggregate)
   {
      ...
   }
'in' is already an operator, so that wouldn't work.
 which could quite peacefully coexist with good-ole
 
   for (int i=0; i<10; i++)
   {
   }
 
 Oh well.  Too much momentum behind foreach and foreach_reverse now, I 
 suppose, to make such a simplification.
foreach is a fairly well understood term, I think it needs less explanation than a variant on for.
Oct 30 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 Just like some people have said, you can do everything with 'for'.
 Well, actually maybe you can.  It occurred to me that foreach and for 
 really don't clash.  One requires two semicolons, the other only one. 
 So instead of a foreach we could have had

   for(a; aggregate)
   {

   }
That looks like a syntax error for one used to for loops.
For one used to C for loops, yes. But Python for loops look like for a in aggregate: ... Bash for loops look like for f in list; do ... done There is certainly precedent for using 'for' synonymously with 'for each'.
 or if the keyword 'in' had been used for the new construct instead of 
 borrowing from c's ; syntax it would be:

   for(a in aggregate)
   {
      ...
   }
'in' is already an operator, so that wouldn't work.
It already has multiple duties as a storage type, so that alone shouldn't be an issue. But I suppose you're right that it won't work here since a standard for loop can start with an Expression, which can contain an 'in'. So this would require arbitrary lookahead to see if there are any ';''s coming or not. Anyway, it wouldn't be a problem in a foreach :-). foreach can only start with ForeachTypeList so there would be no problems with: foreach(a in aggregate) { } as far as I can tell.
 which could quite peacefully coexist with good-ole

   for (int i=0; i<10; i++)
   {
   }

 Oh well.  Too much momentum behind foreach and foreach_reverse now, I 
 suppose, to make such a simplification.
foreach is a fairly well understood term, I think it needs less explanation than a variant on for.
Perhaps. But we're talking about epsilon vs 2*epsilon in terms of the amount of explanation required. One thing foreach has going for it is that it makes it feasible to replace the ';' with the 'in' keyword. :-) --bb
Oct 30 2006
parent Burton Radons <burton-radons smocky.com> writes:
Bill Baxter wrote:
 Walter Bright wrote:
 Bill Baxter wrote:
 Just like some people have said, you can do everything with 'for'.
 Well, actually maybe you can.  It occurred to me that foreach and for 
 really don't clash.  One requires two semicolons, the other only one. 
 So instead of a foreach we could have had

   for(a; aggregate)
   {

   }
That looks like a syntax error for one used to for loops.
For one used to C for loops, yes. But Python for loops look like for a in aggregate: ... Bash for loops look like for f in list; do ... done There is certainly precedent for using 'for' synonymously with 'for each'.
Who is more likely to move to D - a C/C++ programmer who also knows Python/Bash, or a dedicated Python or Bash programmer?
 or if the keyword 'in' had been used for the new construct instead of 
 borrowing from c's ; syntax it would be:

   for(a in aggregate)
   {
      ...
   }
'in' is already an operator, so that wouldn't work.
It already has multiple duties as a storage type, so that alone shouldn't be an issue. But I suppose you're right that it won't work here since a standard for loop can start with an Expression, which can contain an 'in'. So this would require arbitrary lookahead to see if there are any ';''s coming or not.
Arbitrary lookahead is no problem for D (it has to do this anyway, a lot, and this is a good thing as it's more important for a language to be comprehensible to the user, and minimise redundant information, than it is for it to be simple to parse), and is makes logical sense, as it iterates over all "a" which are in "aggregate". For example: where (uint a; a < 100) // Iterate from 0 to 99. where (a; a >= 0 && a < 100 && a % 2) // Iterate over every odd value from 0 to 99. where (a; a in list1 && a in list2) // Set intersection. where (a; a in list1 || a in list2) // Set union. where (a; a in list1 && a !in list2) // Set difference. auto list3 = where (a; a in list1 && a in list2); // Wee! This is pretty easy to do terribly, somewhat harder to do usefully, very hard to do intelligently, and incomprehensibly hard to do at peak optimisability in all cases. I'm not proposing it as a D feature, it's very much not in its style and it's something you'd spend twenty years progressively optimising, but it would be fun to have these kinds of advanced data-processing features in a fast language. So long as we're in fantasy-land: where (a; 0 <= a < 100) Ah, that's better. C's idea that logical operations belong on the same continuum as equations is so very, very 60s. And if we're no longer working with such outdated logic: where (a; a in (list1 && list2)); Makes sense to me.
Nov 01 2006
prev sibling next sibling parent reply Reiner Pope <reiner.pope REMOVE.THIS.gmail.com> writes:
I don't see much reason to change the syntax here, since:
  - it provides no generalizations/abstractions
  - it only saves 4 keys of typing
  - it isn't easier to read; perhaps even *harder* (you don't know 
whether it is a custom iterator or a 'normal' for loop)
  - Walter's right: it could increase bugs, such as:

      for (char[] key, char[] value; aggregate)

         which looks similar to

      for (char[] key; char[] value; aggregate)

Cheers,

Reiner
Oct 30 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Reiner Pope wrote:
 I don't see much reason to change the syntax here, since:
  - it provides no generalizations/abstractions
It generalizes 'for'.
  - it only saves 4 keys of typing
It's more about saving a keyword in the grammar than saving typing.
  - it isn't easier to read; perhaps even *harder* (you don't know 
 whether it is a custom iterator or a 'normal' for loop)
Yeh, that's sort of true, but 90% of regular for loops have a ++ or -- or += or .next() in them at the end that make them pretty easy to recognize as regular for loops. Any for loop that doesn't fit that pattern already takes some scrutiny to figure out.
  - Walter's right: it could increase bugs, such as:
 
      for (char[] key, char[] value; aggregate)
 
         which looks similar to
 
      for (char[] key; char[] value; aggregate)
That'd be a syntax error. You cannot declare variables in the second clause of a standard for-loop. You can only have an Expression there. But yeh, I'm sure you can cook up some example where you change one comma to a semicolon and both are legal. Anyway, the real answer is trailing delegates. This wasn't a serious proposal for change, more just musing on what could have been. foreach didn't really have to be a keyword, I don't think, that's the only observation I was trying to make. --bb
Oct 30 2006
next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Bill Baxter wrote:
 But yeh, I'm sure you can cook up some example where you change one 
 comma to a semicolon and both are legal.
for (a, b; c) for (a; b; c) Not only do they look very, very similar, it is not at all obvious which one was intended.
Oct 30 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 But yeh, I'm sure you can cook up some example where you change one 
 comma to a semicolon and both are legal.
for (a, b; c) for (a; b; c) Not only do they look very, very similar, it is not at all obvious which one was intended.
Assuming you mean literally a b and c, it's pretty clear it's got to be a foreach that was intended. There's no reason to stick a variable in either the first or last clause of a standard for. Here's a example where the intent really isn't obvious: for (int a; b;) for (int a; b) Anyway, given that * I myself have been guilty of using ',' where I meant ';' in for loops, * 'in' can't be used in place of ';' due to syntactical ambiguities, I agree that it's safer and better to have foreach separate. --bb
Oct 30 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Bill Baxter wrote:
 Assuming you mean literally a b and c, it's pretty clear it's got to be 
 a foreach that was intended.  There's no reason to stick a variable in 
 either the first or last clause of a standard for.
There is if a and b are function names.
 Here's a example where the intent really isn't obvious:
 
    for (int a; b;)
    for (int a; b)
 
 Anyway, given that
 * I myself have been guilty of using ',' where I meant ';' in for loops,
 * 'in' can't be used in place of ';' due to syntactical ambiguities,
 
 I agree that it's safer and better to have foreach separate.
 
 --bb
Oct 30 2006
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 Assuming you mean literally a b and c, it's pretty clear it's got to 
 be a foreach that was intended.  There's no reason to stick a variable 
 in either the first or last clause of a standard for.
There is if a and b are function names.
I see. I didn't realize this property stuff extended to plain functions too. I would be strongly inclined to wedgie anyone who made extensive use of that feature. :-) --bb
Oct 30 2006
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Bill Baxter wrote:
 Reiner Pope wrote:
 I don't see much reason to change the syntax here, since:
  - it provides no generalizations/abstractions
It generalizes 'for'.
No it doesn't. It provides no semantic generalization/orthogonality *as well as* no syntactic generalization/orthogonality either. If you think of the D grammar, the 'foreach' rule is removed, but another, similar one is added to the 'for' rule alternatives. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 31 2006
prev sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
I got so used to foreach in D, I've just ported it to C ;)

namespace D {
template <typename T, int S>
inline size_t length( T (&a)[S] ) { return S; }
// Add overloads for your own containers
}

#define foreach(ELEMENT,ARRAY) \
  for(size_t __L=D::length(ARRAY),__B=0,__I=0; !__B && __I<__L; 
assert(__L==D::length(ARRAY)), ++__I ) \
    if (__B = 1) \
      for(ELEMENT = ARRAY[__I]; __B; __B=0) // a break must make the main 
loop stop

Don't get too scared; Microsoft's compiler does a great job optimizing that 
loop. In fact, if you don't use 'break', the second loop will disappear 
completely. I haven't checked the generated code on other compilers yet.

You use it like this (yes, it's C):

int nums[] = {1,2,3,4};
foreach(int i,nums) { }
foreach(int &i,nums) { i=2; }

L.
Oct 30 2006
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Lionello Lunesu wrote:
 I got so used to foreach in D, I've just ported it to C ;)
 
 namespace D {
 template <typename T, int S>
 inline size_t length( T (&a)[S] ) { return S; }
 // Add overloads for your own containers
 }
 
 #define foreach(ELEMENT,ARRAY) \
   for(size_t __L=D::length(ARRAY),__B=0,__I=0; !__B && __I<__L; 
 assert(__L==D::length(ARRAY)), ++__I ) \
     if (__B = 1) \
       for(ELEMENT = ARRAY[__I]; __B; __B=0) // a break must make the main 
 loop stop
I think you have a dangling-else bug here: if (whatever) foreach(elt, arr) { something(); } else somethingelse(); What happens in this code? It looks like somethingelse() will be executed if whatever evaluates to false, but IIRC the 'else' clause will bind to the 'if' you have in your macro. Since that one never evaluates to false, somethingelse() will never be called here. I haven't tried it though, this was just from looking at it. Also, this won't manifest itself if you always use braces after 'if's so this may be hard to spot the one time you forget :).
Oct 31 2006
parent Lionello Lunesu <lio lunesu.remove.com> writes:
Frits van Bommel wrote:
 Lionello Lunesu wrote:
 I got so used to foreach in D, I've just ported it to C ;)

 namespace D {
 template <typename T, int S>
 inline size_t length( T (&a)[S] ) { return S; }
 // Add overloads for your own containers
 }

 #define foreach(ELEMENT,ARRAY) \
   for(size_t __L=D::length(ARRAY),__B=0,__I=0; !__B && __I<__L; 
 assert(__L==D::length(ARRAY)), ++__I ) \
     if (__B = 1) \
       for(ELEMENT = ARRAY[__I]; __B; __B=0) // a break must make the 
 main loop stop
I think you have a dangling-else bug here: if (whatever) foreach(elt, arr) { something(); } else somethingelse(); What happens in this code? It looks like somethingelse() will be executed if whatever evaluates to false, but IIRC the 'else' clause will bind to the 'if' you have in your macro. Since that one never evaluates to false, somethingelse() will never be called here. I haven't tried it though, this was just from looking at it. Also, this won't manifest itself if you always use braces after 'if's so this may be hard to spot the one time you forget :).
Good one, easily solvable though, by inverting __B and using "if () {} else for...".. Thanks! L.
Oct 31 2006
prev sibling parent Walter Bright <newshound digitalmars.com> writes:
Lionello Lunesu wrote:
 I got so used to foreach in D, I've just ported it to C ;)
I believe Eric Niebler wrote one too for C++ using preprocessor wizardry. I've never been able to figure out how it worked <g>.
Oct 31 2006