www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Another foreach idea.

reply Dave <Dave_member pathlink.com> writes:
Instead of:

for(int i = 1_000; i < 1_000_000; i++) {}

How about:

foreach(idx; 1_000 .. 1_000_000)
{
     // 1_000 to 999_999 or 1_000 to 1_000_000?
     // type inferred 'idx' would be an int or long
     //  depending on 32 bit or 64 bit hardware
}

or simply:

size_t x = 1_000_000;

foreach(idx; x)
{
     // 0 to 999_999 or 1 to 1_000_000?
     // type inferred 'idx' is size_t
}

The rational would be not only for brevity but also the reasons given 
here: http://digitalmars.com/d/faq.html#foreach

I can see the problems already - 0 or 1 based? 0 based to be consistent 
with how many for loops are written now and with array indexing (even 
though presumably you'd then use foreach with the array itself)? Or 1 
based because then it would be WYSIWYG.

- Dave
Jun 07 2006
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Dave wrote:
 
 Instead of:
 
 for(int i = 1_000; i < 1_000_000; i++) {}
 
 How about:
 
 foreach(idx; 1_000 .. 1_000_000)
 {
     // 1_000 to 999_999 or 1_000 to 1_000_000?
     // type inferred 'idx' would be an int or long
     //  depending on 32 bit or 64 bit hardware
 }
 
 or simply:
 
 size_t x = 1_000_000;
 
 foreach(idx; x)
 {
     // 0 to 999_999 or 1 to 1_000_000?
     // type inferred 'idx' is size_t
 }
 
 The rational would be not only for brevity but also the reasons given 
 here: http://digitalmars.com/d/faq.html#foreach
 
 I can see the problems already - 0 or 1 based? 0 based to be consistent 
 with how many for loops are written now and with array indexing (even 
 though presumably you'd then use foreach with the array itself)? Or 1 
 based because then it would be WYSIWYG.
 
 - Dave

This is very much like Python's behavior. In that language, there isn't a "for" loop as such, only a "foreach" loop. To loop a counter from 0 to 9, you'd write: for i in range(10): print i Where "range" is a function that returns a list containing a range of integers, with a footprint like: range(start=0, end, step=1) (Python allows default values in places that D does not.) There is also an xrange function, which returns a generator rather than a whole list, which can be more useful for large ranges. To loop from 10 to 99, you'd write: for i in range(10, 100): print i In short, all you need is a "range" function that returns an array of integers, or (alternately) a generator... something like this: private import std.stdio; class range { protected: int m_start, m_end; int m_step = 1; public: // Can't just specify (end, step), but there's nothing // to be done for that. this(int end) { m_end = end; } this(int start, int end) { m_start = start; this(end); } this(int start, int end, int step) { m_step = step; this(start, end); } // Convenience functions static range opCall(int end) { return new range(end); } static range opCall(int start, int end) { return new range(start, end); } static range opCall(int start, int end, int step) { return new range(start, end, step); } // Compares whether i has reached the end private bool cmp(int i) { if (m_step > 0 && i < m_end) return true; else if (m_step < 0 && i > m_end) return true; else return false; } int opApply(int delegate(inout int) dg) { int result = 0; for (int i=m_start; this.cmp(i); i+=m_step) { result = dg(i); if (result) break; } return result; } } void main() { foreach(i; range(10)) { writefln("%s", i); } foreach(j; range(10, 20)) { writefln("%s", j); } foreach(k; range(0, 10, 2)) { writefln("%s", k); } } Though honestly, a regular for loop isn't much of a burden. -Kirk McDonald
Jun 07 2006
next sibling parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Kirk McDonald wrote:
 In short, all you need is a "range" function that returns an array of 
 integers, or (alternately) a generator... something like this:
 

Oh! I did some thinking and improved it a little. It's a struct now, so it doesn't need to touch the GC. And factory functions are maybe a little more clear than static opCalls (also, there are two, now). private import std.stdio; struct range_struct { public: int m_start, m_end, m_step; // Compares whether i has reached the end private bool cmp(int i) { if (m_step > 0 && i < m_end) return true; else if (m_step < 0 && i > m_end) return true; else return false; } int opApply(int delegate(inout int) dg) { int result = 0; for (int i=m_start; this.cmp(i); i+=m_step) { result = dg(i); if (result) break; } return result; } } range_struct range(int start, int end, int step=1) { range_struct r; r.m_start = start; r.m_end = end; r.m_step = step; return r; } range_struct range(int end) { return range(0, end, 1); } void main() { foreach(i; range(10)) { writefln("%s", i); } foreach(j; range(10, 20)) { writefln("%s", j); } foreach(k; range(0, 10, 2)) { writefln("%s", k); } } -Kirk McDonald
Jun 07 2006
next sibling parent zadok <zadokallen yahoo.com> writes:
Kirk McDonald wrote:
 Kirk McDonald wrote:
 In short, all you need is a "range" function that returns an array of 
 integers, or (alternately) a generator... something like this:

Oh! I did some thinking and improved it a little. It's a struct now, so it doesn't need to touch the GC. And factory functions are maybe a little more clear than static opCalls (also, there are two, now). private import std.stdio; struct range_struct { public: int m_start, m_end, m_step; // Compares whether i has reached the end private bool cmp(int i) { if (m_step > 0 && i < m_end) return true; else if (m_step < 0 && i > m_end) return true; else return false; } int opApply(int delegate(inout int) dg) { int result = 0; for (int i=m_start; this.cmp(i); i+=m_step) { result = dg(i); if (result) break; } return result; } } range_struct range(int start, int end, int step=1) { range_struct r; r.m_start = start; r.m_end = end; r.m_step = step; return r; } range_struct range(int end) { return range(0, end, 1); } void main() { foreach(i; range(10)) { writefln("%s", i); } foreach(j; range(10, 20)) { writefln("%s", j); } foreach(k; range(0, 10, 2)) { writefln("%s", k); } } -Kirk McDonald

make your struct predefined in the language with the syntaxic sugar start..end and this would be fine to compile: range r = 0..9; assert(r == range(0, 9)); r = range(0, 9, 2); char[] str = "0123456789"; assert(str[r] == "02468");
Jun 07 2006
prev sibling parent reply Derek Parnell <derek psych.ward> writes:
On Wed, 07 Jun 2006 22:56:50 -0700, Kirk McDonald wrote:

 Kirk McDonald wrote:

 Oh! I did some thinking and improved it a little. It's a struct now ...

Hope you don't mind but I turned your code into a templated version... //------- file: ranger.d ---------------- struct range_struct(T) { public: T m_start, m_end, m_step; // Compares whether i has reached the end private bool cmp(T i) { if (m_step > 0 && i < m_end) return true; else if (m_step < 0 && i > m_end) return true; else return false; } int opApply(int delegate(inout T) dg) { int result = 0; for (T i=m_start; this.cmp(i); i+=m_step) { result = dg(i); if (result) break; } return result; } int opApply(int delegate(inout int, inout T) dg) { int result = 0; int cnt; for (T i=m_start; this.cmp(i); i+=m_step) { result = dg(cnt, i); if (result) break; cnt++; } return result; } } template range_t(T) { range_struct!(T) range(T start, T end, T step=1) { range_struct!(T) r; r.m_start = start; r.m_end = end; r.m_step = step; return r; } range_struct!(T) range(T end) { return range(0, end, 1); } } alias range_t!(char).range range; alias range_t!(int).range range; alias range_t!(real).range range; //--------- FILE END ----------- // -------file: test.d --------- import std.stdio; import ranger; void main() { foreach(i; range(10)) { writefln("i %s", i); } foreach(j; range(10, 20)) { writefln("j %s", j); } foreach(q, k; range(10.0, 0.0, -0.9)) { writefln("k %s %s", q, k); } foreach(q, l; range('A', cast(char)('Z'+1))) { writefln("l %s %s", q, l); } } //--------- FILE END ----------- When run with ... build c:\temp\test.d -exec I get ... Path and Version : y:\util\build.exe v3.00(1964) built on Tue Jun 6 10:38:29 2006 i 0 i 1 i 2 i 3 i 4 i 5 i 6 i 7 i 8 i 9 j 10 j 11 j 12 j 13 j 14 j 15 j 16 j 17 j 18 j 19 k 0 10 k 1 9.1 k 2 8.2 k 3 7.3 k 4 6.4 k 5 5.5 k 6 4.6 k 7 3.7 k 8 2.8 k 9 1.9 k 10 1 k 11 0.1 l 0 A l 1 B l 2 C l 3 D l 4 E l 5 F l 6 G l 7 H l 8 I l 9 J l 10 K l 11 L l 12 M l 13 N l 14 O l 15 P l 16 Q l 17 R l 18 S l 19 T l 20 U l 21 V l 22 W l 23 X l 24 Y l 25 Z Neat eh? -- Derek (skype: derek.j.parnell) Melbourne, Australia "Down with mediocrity!" 8/06/2006 4:41:43 PM
Jun 07 2006
next sibling parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Derek Parnell wrote:
 On Wed, 07 Jun 2006 22:56:50 -0700, Kirk McDonald wrote:
Oh! I did some thinking and improved it a little. It's a struct now ...

Hope you don't mind but I turned your code into a templated version...

Sweet!
Jun 08 2006
prev sibling parent Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Derek Parnell wrote:
 On Wed, 07 Jun 2006 22:56:50 -0700, Kirk McDonald wrote:
 
 Kirk McDonald wrote:

 Oh! I did some thinking and improved it a little. It's a struct now ...

Hope you don't mind but I turned your code into a templated version... //------- file: ranger.d ---------------- struct range_struct(T) { public: T m_start, m_end, m_step; // Compares whether i has reached the end private bool cmp(T i) { if (m_step > 0 && i < m_end) return true; else if (m_step < 0 && i > m_end) return true; else return false; } int opApply(int delegate(inout T) dg) { int result = 0; for (T i=m_start; this.cmp(i); i+=m_step) { result = dg(i); if (result) break; } return result; } int opApply(int delegate(inout int, inout T) dg) { int result = 0; int cnt; for (T i=m_start; this.cmp(i); i+=m_step) { result = dg(cnt, i); if (result) break; cnt++; } return result; } }

<replace>
 template range_t(T)
 {
     range_struct!(T) range(T start, T end, T step=1) {
          range_struct!(T) r;
          r.m_start = start;
          r.m_end   = end;
          r.m_step  = step;
          return r;
     }
 
     range_struct!(T) range(T end) {
          return range(0, end, 1);
     }
 }
 
 alias range_t!(char).range range;
 alias range_t!(int).range range;
 alias range_t!(real).range range;

Replacing the above code with the following (kind of ugly) code makes it work for any type that is comparable, implements += and (this is limiting but possibly workaroundable with some assumptions) assignable from 1 and 0. template range(T,T2,T3=int) { static assert(is(T2:T), "range arg 2 must be convertible to T"); static assert(is(T3:T), "range arg 3 must be convertible to T"); range_struct!(T) range(T start, T2 end, T3 step=1) { range_struct!(T) r; r.m_start = start; r.m_end = end; r.m_step = step; return r; } } template range(T) { range_struct!(T) range(T end) { return .range!(T,T,T)(0,end,1); } } /Oskar
Jun 08 2006
prev sibling parent reply Dave <Dave_member pathlink.com> writes:
Kirk McDonald wrote:
 Dave wrote:
 I can see the problems already - 0 or 1 based? 0 based to be 
 consistent with how many for loops are written now and with array 
 indexing (even though presumably you'd then use foreach with the array 
 itself)? Or 1 based because then it would be WYSIWYG.

This is very much like Python's behavior. In that language, there isn't a "for" loop as such, only a "foreach" loop. To loop a counter from 0 to 9, you'd write: for i in range(10): print i

Thanks for the info. on Python. Since there's precedent with Python, I guess '0 based' ranges would settle that.
 
 Though honestly, a regular for loop isn't much of a burden.
 

You're right, it isn't, until you run into some time critical loops that you'd like to leave to the compiler to optimize. And a loop-invariant condition and 'range' could allow foreach to better do things like loop unrolling or use vector instructions, I think. It'd be one more thing that sets D apart too.
 -Kirk McDonald

Jun 08 2006
parent reply Fredrik Olsson <peylow gmail.com> writes:
Dave skrev:
 Kirk McDonald wrote:
 
 Dave wrote:


 Though honestly, a regular for loop isn't much of a burden.

You're right, it isn't, until you run into some time critical loops that you'd like to leave to the compiler to optimize. And a loop-invariant condition and 'range' could allow foreach to better do things like loop unrolling or use vector instructions, I think. It'd be one more thing that sets D apart too.

I can take this as an opportunity to yet again voice my opinion for implementing ranges and sets as basic types of D. Templates are good and all, but as Walter writes himself on proper handling of floating point and infinity: 1. Building it in to the core language means the core language knows what a floating point infinity is. Being layered in templates, typedefs, casts, const bit patterns, etc., it doesn't know what it is, and is unlikely to give sensible error messages if misused. 2. A side effect of (1) is it is unlikely to be able to use it effectively in constant folding and other optimizations. I think the same thing goes for ranges, ranged integer types and sets. The expressive power and the productivity boost they give is just too huge to be ignored. More work done with less code is _always_ good, less code == less bugs to avoid, find, and correct. // Fredrik Olsson
Jun 13 2006
parent "Andrei Khropov" <andkhropov nospam_mtu-net.ru> writes:
Fredrik Olsson wrote:

 I can take this as an opportunity to yet again voice my opinion for
 implementing ranges and sets as basic types of D. Templates are good and all,
 but as Walter writes himself on proper handling of floating point and
 infinity:  1. Building it in to the core language means the core language
 knows what a floating point infinity is. Being layered in templates,
 typedefs, casts, const bit patterns, etc., it doesn't know what it is, and is
 unlikely to give sensible error messages if misused.  2. A side effect of (1)
 is it is unlikely to be able to use it effectively in constant folding and
 other optimizations.
 
 I think the same thing goes for ranges, ranged integer types and sets.

Absolutely agree. My vote for it! -- AKhropov
Jun 18 2006