February 14, 2007
Dave wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Sean Kelly wrote:
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> Frits van Bommel wrote:
>>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>>> Bill Baxter wrote:
>>>>>>> Yeh, I don't get it either.  How would that help me implement merge() from merge sort for instance?
>>>>>>
>>>>>> Merge bumps the iteration in both collections conditionally. The form above bumps the iteration in the two collections unconditionally, until one is finished; then it continues with the other until that is finished.
>>>>>
>>>>> In other words, it doesn't :(.
>>>>
>>>> A need for loops iterating over multiple collections depending on arbitrary conditions will always be there. The point of extending foreach is to address the often-encountered case when you want to iterate over multiple collections simultaneously (e.g.: copy a collection to another), just like foreach itself is addressing the particular but frequent case of iterating one collection in a linear manner.
>>>
>>> What about:
>>>
>>>     foreach (i ; coll1) (j ; coll2)
>>>     {
>>>         if( true )
>>>             continue i;
>>>     }
>>>
>>> ie. allow 'continue' to accept labels to specify which collection is iterated.  A 'continue' without labels would iterate both.
>>
>> I think that's a great idea, except that "continue to label" has the same syntax: http://digitalmars.com/d/statement.html#ContinueStatement
>>
>> Andrei
> 
> How about using 'next' to keep it simple, so the compiler doesn't have to create / check for 'i' and 'j' as lables with the same function scope:
> 
> i:  while(...)
>     {
>        foreach (i ; coll1) (j ; coll2)
>        {
>            if( true )
>                continue i;
>            if( i < j )
>                next i;
>        }
>     }
> ?

I think it's very desirable to add keywords only when there's an absolute must.

Andrei
February 14, 2007
Kirk McDonald wrote:
> Sean Kelly wrote:
>> Derek Parnell wrote:
>>
>>> On Tue, 13 Feb 2007 17:39:46 +0100, Frits van Bommel wrote:
>>>
>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>
>>>>> Bill Baxter wrote:
>>>>>
>>>>>> Yeh, I don't get it either.  How would that help me implement merge() from merge sort for instance?
>>>>>
>>>>> Merge bumps the iteration in both collections conditionally. The form above bumps the iteration in the two collections unconditionally, until one is finished; then it continues with the other until that is finished.
>>>>
>>>> In other words, it doesn't :(.
>>>
>>>
>>> I imaging that the full syntax will also include this form ...
>>>
>>>   foreach (int x, i ; coll1) (int y, j ; coll2)   {
>>>    ... use i and j ...
>>>      if (somecondition)
>>>         x = ...  // To set the index back or forward to some
>>>                  // arbitary point in the array 'coll1'.
>>>   }
>>
>>
>> This currently works for built-in arrays but not for user-defined types.  Also, I think the fact that it works as all is the result of an implementation detail, not spec-defined behavior.
>>
>>
>> Sean
> 
> There's no reason a user-defined type couldn't implement this.

Exactly. Walter and I have bounced a couple of possibilities and concluded that the feature is of "medium difficulty/medium usefulness". Probably Walter will look into implementing this first:

foreach (i ; 0 .. n)
{
  ...
}


Andrei
February 14, 2007
Sean Kelly wrote:
> Derek Parnell wrote:
>> On Tue, 13 Feb 2007 17:39:46 +0100, Frits van Bommel wrote:
>>
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> Bill Baxter wrote:
>>>>> Yeh, I don't get it either.  How would that help me implement merge() from merge sort for instance?
>>>> Merge bumps the iteration in both collections conditionally. The form above bumps the iteration in the two collections unconditionally, until one is finished; then it continues with the other until that is finished.
>>> In other words, it doesn't :(.
>>
>> I imaging that the full syntax will also include this form ...
>>
>>   foreach (int x, i ; coll1) (int y, j ; coll2)   {
>>    ... use i and j ...
>>      if (somecondition)
>>         x = ...  // To set the index back or forward to some
>>                  // arbitary point in the array 'coll1'.
>>   }
> 
> This currently works for built-in arrays but not for user-defined types.  Also, I think the fact that it works as all is the result of an implementation detail, not spec-defined behavior.

Besides it's awkward to have to decrement x if you want to "stay there" just so that you cancel the next increment. foreach is best for straight loops; in fact I think of it as the functional "map" as foreach (assuming the above gets fixed) has no imperative element to it.

Andrei
February 14, 2007
X Bunny wrote:
> Kevin Bealer wrote:
>> X Bunny wrote:
>>> Kevin Bealer wrote:
>>>>
>>>> 2. The syntax doesn't provide visual hints that let you read a program.
>> What I mean, is this (from the Computer Language Shootout):
>>
>> (defun ack (x y)
>>   (declare (fixnum x y))
>>   (the fixnum
>>     (if (zerop x)
>>     (1+ y)
>>       (if (zerop y)
>>       (ack (1- x) 1)
>>     (ack (1- x) (ack x (1- y)))))))
>>

> My editor indents the Lisp like this:
> 
> (defun ack (x y)
>   (declare (fixnum x y))
>   (the fixnum
>     (if (zerop x)
>     (1+ y)
>       (if (zerop y)
>       (ack (1- x) 1)
>     (ack (1- x) (ack x (1- y)))))))
> 

hmm that looks exactly the same in my news reader that means (a) my formatting is screwed up by the newsreader or server somewhere and (b) the original was probably indented correctly also before it was posted or when I read it. Therefore I guess you wouldnt agree that correct indenting made it as easily readable as the C, oh well then. It should have the true expression of the if offset four characters from the logical expression and the else expression two characters.
February 14, 2007
Frits van Bommel wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Sean Kelly wrote:
>>> What about:
>>>
>>>     foreach (i ; coll1) (j ; coll2)
>>>     {
>>>         if( true )
>>>             continue i;
>>>     }
>>>
>>> ie. allow 'continue' to accept labels to specify which collection is iterated.  A 'continue' without labels would iterate both.
>>
>> I think that's a great idea, except that "continue to label" has the same syntax: http://digitalmars.com/d/statement.html#ContinueStatement
> 
> Does that really matter? The compiler knows whether 'i' is a label or a loop variable (presumably it can't be both at the same time?) so it knows what to do. Note that the current "continue to label" wouldn't help here since there's only one statement for a "double" iteration. So the most natural way to specify which loop to continue would be to specify the variable.

That's a good point, but changing names could complicate maintenance.

> By the way, would the new loop syntax allow more than two collections to be simultaneously iterated?
> That would indicate the need for a "continue i, j" as well, to specify multiple variables.
> On the other hand, with your proposed "continue foreach" clauses after the main loop that would also require an exponential number of those clauses for different sets of collections running out if you want to handle all cases...

That is correct. But it's not a real issue for a simple reason: the user would have to write the code. If they need to handle all cases, well, that's what they need to do one way or another. The foreach statement does not add anything to the equation.

Of course, in most cases the user has some prior constraints on the sizes so they know which "continue foreach" sections must be written. Let me clarify that the continue foreach statements are optional, not required.


Andrei
February 14, 2007
X Bunny wrote:
> (defun ack (x y)
>   (declare (fixnum x y))
>   (the fixnum
>     (if (zerop x)
>     (1+ y)
>       (if (zerop y)
>       (ack (1- x) 1)
>     (ack (1- x) (ack x (1- y)))))))
> 
> The structure is no less obvious to me then the C. I can see the input and output types are clearly fixnums. The branches of the ifs are obvious.

I see:
	1- x
in the Lisp code, and have to mentally translate it to:
	x - 1
and not:
	1 - x

This just hurts my brain.
February 14, 2007
kris wrote:
> Bill Baxter wrote:
>> kris wrote:
>>
>>> kris wrote:
>>>
>>>> Bill Baxter wrote:
>>>>
>>>>> Frits van Bommel wrote:
>>>>>
>>>>>> By the way, would the new loop syntax allow more than two collections to be simultaneously iterated?
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> Whoa!  I certainly hope so.  It hadn't even occurred to me that Andrei might mean this syntax can only be used for just two collections.  If that's the case then ... ick.
>>>>>
>>>>> --bb
>>>>
>>>>
>>>>
>>>> InterleavedIterator does multiple collections via using multiple instances of InterleavedIterator. It's simple to use, and only needs to be written once. Would be better to implement some basic iterator needs than to introduce some tricky new syntax?
>>>>
>>>> - Kris
>>>
>>>
>>>
>>> Should have given an example. Simple case with 2 entities:
>>>
>>> auto two = InterleavedInterator (x, y);
>>> foreach (x; two) ...
>>>
>>> more than 2:
>>>
>>> auto three = InterleavedInterator (two, z);
>>> foreach (x; three) ...
>>
>>
>> Should have also mentioned where one can find this mythical InterleavedIterator.
>>
>> --bb
> 
> There is no 'standard' one at this time that I know of (judging by the discussion on it a while back). However, Tango does have this beastie in the collections package. The point is, coming up with a lightweight core Iterator approach would likely provide a simpler and more dependable solution.

Ok that wasn't clear to me.  It sounded like you were talking about code I could type in today and have it work given suitable (but not specified) imports.

> in the above example, x, y, and z are all iterators themselves. If D had a core notion of Iterator, that's what those would be. For instance, D iterators might map to a delegate (which is what the body of a foreach actually is).

Yeh, basically it's the same as the Python izip that mentioned.  That's python's name for InterleavedIterator.

I think the issue with D right now is that the 'x' returned by a hypothetical InterleavedIterator would ideally be a tuple.  And you would access the elements with x[0],x[1],x[2] (int the 'three' case above).  Or you could do  foreach(x,y,z; three) and have it unpacked for you.

I think it would be great if this kind of stuff worked.  I'm much less excited about a built-in syntax that _only_ knows how to do that one trick.

--bb
February 14, 2007
Bill Baxter wrote:
> kris wrote:
>> kris wrote:
>>> Bill Baxter wrote:
>>>
>>>> Frits van Bommel wrote:
>>>>
>>>>> By the way, would the new loop syntax allow more than two collections to be simultaneously iterated?
>>>>
>>>>
>>>>
>>>> Whoa!  I certainly hope so.  It hadn't even occurred to me that Andrei might mean this syntax can only be used for just two collections.  If that's the case then ... ick.
>>>>
>>>> --bb
>>>
>>>
>>> InterleavedIterator does multiple collections via using multiple instances of InterleavedIterator. It's simple to use, and only needs to be written once. Would be better to implement some basic iterator needs than to introduce some tricky new syntax?
>>>
>>> - Kris
>>
>>
>> Should have given an example. Simple case with 2 entities:
>>
>> auto two = InterleavedInterator (x, y);
>> foreach (x; two) ...
>>
>> more than 2:
>>
>> auto three = InterleavedInterator (two, z);
>> foreach (x; three) ...
> 
> Should have also mentioned where one can find this mythical InterleavedIterator.

The issue with such a multi-iterator is that it makes it easier to make errors, and harder to write efficient and correct code that's statically verifiable.

I'm not sure when the interleaved iterator stops iterating, but there are two possibilities, none of which is satisfactory:

1. Stop after the shortest of the two collections is done. Then user code must query the state of the iterator after the loop to figure what extra work is to be done:

auto two = InterleavedInterator (x, y);
foreach (x; two) { ... }
if (two.MoreData(0)) {
  auto back2one = two.Project(0); // fetch the first iterator
  foreach (x ; back2one) { ... }
} else if (two.moreData(1)) {
  ... same (or)deal ...
}

This is way more work than there should.

2. Stop after the longest of the two collections is done. Then user code must ensure _at each step_ that both iterators have meaningful data:

auto two = InterleavedInterator (x, y);
foreach (x; two) {
  if (two.HasData(0)) { ... }
  else { ... only the second iter has data ... }
}

This is unclear, verbose, and probably suboptimal.

The scoping of foreach links the scope of the variables with their validity range, which rules out a class of possible errors entirely:

foreach (x ; c1) (y ; c2) (z ; c3) {
  ... x, y, z syntactically accessible _and_ valid ...
}
continue foreach (x, z) {
  ... x is both invalid _and_ syntactically inaccessible ...
}

As I mentioned in a different post, the fact that there are combinatorial potential sub-foreach statements is a non-issue.


Andrei
February 14, 2007
Don Clugston wrote:
> To quote Stepanov (the link that Bill Baxter just posted):
> 
> Alexander Stepanov Notes on Programming 10/3/2006

I still can't find the link! What is it?
February 14, 2007
Walter Bright wrote:
> X Bunny wrote:
>> (defun ack (x y)
>>   (declare (fixnum x y))
>>   (the fixnum
>>     (if (zerop x)
>>     (1+ y)
>>       (if (zerop y)
>>       (ack (1- x) 1)
>>     (ack (1- x) (ack x (1- y)))))))
>>
>> The structure is no less obvious to me then the C. I can see the input and output types are clearly fixnums. The branches of the ifs are obvious.
> 
> I see:
>     1- x
> in the Lisp code, and have to mentally translate it to:
>     x - 1
> and not:
>     1 - x
> 
> This just hurts my brain.

Probably it's fair to say that the converse (hurting a LISPer brain with D code) is not hard to imagine either.

Andrei