digitalmars.D - The last changes to range
- Andrei Alexandrescu (36/36) May 29 2010 I plan to make two more (hopefully the last two) changes to the range
- Philippe Sigaud (16/42) May 29 2010 Does that mean that you changed some other parts recently?
- Andrei Alexandrescu (24/68) May 29 2010 Yah.
- Philippe Sigaud (49/118) May 30 2010 OK, I wondered whether std.container had reverberations I wasn't aware o...
- Pelle (4/115) May 30 2010 Worse:
- Andrei Alexandrescu (3/5) May 30 2010 That's a bug in Phobos.
- Andrei Alexandrescu (16/128) May 30 2010 Yah. Essentially R r2 = r1; is not a guarantee that r2 is independent
- Philippe Sigaud (22/72) May 31 2010 OK, understood.
- Andrei Alexandrescu (3/10) May 31 2010 bringToFront.
- Simen kjaeraas (14/36) May 31 2010 I see a solution to this in template lambdas.
- Masahiro Nakagawa (5/21) May 29 2010 I also want save.
- Steven Schveighoffer (9/25) Jun 01 2010 In fact, I'd say you'd want to not define save unless it is the trivial ...
I plan to make two more (hopefully the last two) changes to the range abstraction this morning. 1. First, I want to define this: // inside range type SomeRange property SomeRange save(); That simply returns a copy of the range. Most implementations look like this: // inside range type SomeRange property SomeRange save() { return this; } If SomeRange is a class or interface, you'd write: // inside range type SomeRange property SomeRange save() { return this->clone(); } The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them. 2. swapFront, swapBack, and swapAt methods // inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); All functions are optional and are needed if and only if front(), back(), and opIndex() return rvalues. The idea is to provide a cheap means to move elements in and out of ranges without creating extra copies. There's more detail about this of increased subtlety, please ask. Of course, swapBack() only makes sense if back() exists and swapAt() only makes sense if opIndex exists. 3. sameFront() The gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. I've posted this to the phobos list as well. Andrei
May 29 2010
On Sat, May 29, 2010 at 18:45, Andrei Alexandrescu < SeeWebsiteForEmail erdani.org> wrote:I plan to make two more (hopefully the last two) changes to the range abstraction this morning.Does that mean that you changed some other parts recently?1. First, I want to define this: // inside range type SomeRange property SomeRange save();vote++ That should make it clear you need a forward range for an algorithm.The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them.I think many ranges and algorithm that you put in std have a constraint on InputRange that should be changed to ForwardRange. Most (all?) of the lazy ones should probably ask for a ForwardRange. Don't forget to update that part.2. swapFront, swapBack, and swapAt methods // inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); All functions are optional and are needed if and only if front(), back(), and opIndex() return rvalues. The idea is to provide a cheap means to move elements in and out of ranges without creating extra copies. There's more detail about this of increased subtlety, please ask.Will that be associated with a constraint template? And why methods instead of free functions?3. sameFront() The gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. And if front does not return by ref? Do you then define the fronts to bedifferent or compare the values? About returning by ref, did you try to use 'auto ref'? I think I tried the day the feature appeared, without success, IIRC. Many ranges in Phobos return by ref and won't compose with other ranges because of that. Philippe
May 29 2010
On 05/29/2010 03:05 PM, Philippe Sigaud wrote:On Sat, May 29, 2010 at 18:45, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>> wrote: I plan to make two more (hopefully the last two) changes to the range abstraction this morning. Does that mean that you changed some other parts recently?Not recently, I think I made the last changes before you joined the gang.1. First, I want to define this: // inside range type SomeRange property SomeRange save(); vote++ That should make it clear you need a forward range for an algorithm.Yah.The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them. I think many ranges and algorithm that you put in std have a constraint on InputRange that should be changed to ForwardRange. Most (all?) of the lazy ones should probably ask for a ForwardRange. Don't forget to update that part.I'm not sure about that. Could you give an example? Why would map() not work with an input range?2. swapFront, swapBack, and swapAt methods // inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); All functions are optional and are needed if and only if front(), back(), and opIndex() return rvalues. The idea is to provide a cheap means to move elements in and out of ranges without creating extra copies. There's more detail about this of increased subtlety, please ask. Will that be associated with a constraint template? And why methods instead of free functions?They will be methods because they must be primitive operations of the respective ranges. However, there will be wrappers like this: // at module scope void swapFront(Range)(Range r1, Range r2) { static if (is(typeof(&(r1.front)) == ElementType!(Range)*)) { swap(r1.front, r2.front); } else { static assert(is(typeof(&(r1.swapFront)), "Cannot swap ranges"); r1.swapFront(r2); } }3. sameFront() The gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. And if front does not return by ref? Do you then define the fronts to be different or compare the values?If front() does not return by ref, the range should define sameFront() as a member. If it doesn't, it won't have access to a number of algorithms.About returning by ref, did you try to use 'auto ref'? I think I tried the day the feature appeared, without success, IIRC. Many ranges in Phobos return by ref and won't compose with other ranges because of that.Yah, auto ref was meant for that kind of work. But generally note that certain ranges actively refuse to return by ref in order to not expose addresses of their elements. Such ranges are fit for perfectly encapsulated containers. Andrei
May 29 2010
On Sat, May 29, 2010 at 23:30, Andrei Alexandrescu < SeeWebsiteForEmail erdani.org> wrote:On 05/29/2010 03:05 PM, Philippe Sigaud wrote:OK, I wondered whether std.container had reverberations I wasn't aware of. Btw, I plan to play with trees and graphs and algorithms on them. I will modify my code to respect your container interface and see what sticks. 1. First, I want to define this:Does that mean that you changed some other parts recently?Not recently, I think I made the last changes before you joined the gang.Will the definition of a forward range change to incorporate save, or do you intend to distinguish ranges that can do R r2 = r1; and those that have: auto r2 = r1.save; ? Until now, they were one and the same to me.// inside range type SomeRange property SomeRange save(); vote++ That should make it clear you need a forward range for an algorithm.Yah.The idea is that save() provides a guaranteed means to take aBecause you make a copy of the input range in Map's constructor? this(Range input) { _input = input; fillCache; } I supposed _input = input was not possible with an input range? It's the very definition of a forward range, no? An eager version of map could use an InputRange as input: template eagerMap(alias fun) { typeof(unaryFun!fun(ElementType!R.init))[] eagerMap(R)(R r) if (isInputRange!R && !isInfinite!R) { typeof(unaryFun!fun(ElementType!R.init))[] mapped; static if (hasLength!R) { mapped.length = r.length; foreach(i, elem; r) mapped[i] = unaryFun!fun(elem); } else { foreach(elem; r) mapped ~= unaryFun!fun(elem); // maybe with an ArrayAppender? } return mapped; } }snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them. I think many ranges and algorithm that you put in std have a constraint on InputRange that should be changed to ForwardRange. Most (all?) of the lazy ones should probably ask for a ForwardRange. Don't forget to update that part.I'm not sure about that. Could you give an example? Why would map() not work with an input range?2. swapFront, swapBack, and swapAt methodsOK. I really like this possibility to test for members and activate them when possible. Maybe it could be abstracted away into a Select-like template? 3. sameFront()// inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); They will be methods because they must be primitive operations of therespective ranges. However, there will be wrappers like this: // at module scope void swapFront(Range)(Range r1, Range r2) { static if (is(typeof(&(r1.front)) == ElementType!(Range)*)) { swap(r1.front, r2.front); } else { static assert(is(typeof(&(r1.swapFront)), "Cannot swap ranges"); r1.swapFront(r2); } }OK. So it's really sameFront and not equalFront or somesuch. About returning by ref, did you try to use 'auto ref'? I think I triedThe gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. And if front does not return by ref? Do you then define the fronts to be different or compare the values?If front() does not return by ref, the range should define sameFront() as a member. If it doesn't, it won't have access to a number of algorithms.I was thinking of frustrating obstacles like: auto m = map!"a*a"([0,1,2,3]); auto c = cycle(m); // won't compile, m.front is not an lvalue, whereas c.front asks for one. Putting auto ref front() {...} in Cycle does not change anything. Too bad. Philippethe day the feature appeared, without success, IIRC. Many ranges in Phobos return by ref and won't compose with other ranges because of that.Yah, auto ref was meant for that kind of work. But generally note that certain ranges actively refuse to return by ref in order to not expose addresses of their elements. Such ranges are fit for perfectly encapsulated containers.
May 30 2010
On 05/30/2010 12:11 PM, Philippe Sigaud wrote:On Sat, May 29, 2010 at 23:30, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>> wrote: On 05/29/2010 03:05 PM, Philippe Sigaud wrote: Does that mean that you changed some other parts recently? Not recently, I think I made the last changes before you joined the gang. OK, I wondered whether std.container had reverberations I wasn't aware of. Btw, I plan to play with trees and graphs and algorithms on them. I will modify my code to respect your container interface and see what sticks. 1. First, I want to define this: // inside range type SomeRange property SomeRange save(); vote++ That should make it clear you need a forward range for an algorithm. Yah. Will the definition of a forward range change to incorporate save, or do you intend to distinguish ranges that can do R r2 = r1; and those that have: auto r2 = r1.save; ? Until now, they were one and the same to me. The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them. I think many ranges and algorithm that you put in std have a constraint on InputRange that should be changed to ForwardRange. Most (all?) of the lazy ones should probably ask for a ForwardRange. Don't forget to update that part. I'm not sure about that. Could you give an example? Why would map() not work with an input range? Because you make a copy of the input range in Map's constructor? this(Range input) { _input = input; fillCache; } I supposed _input = input was not possible with an input range? It's the very definition of a forward range, no? An eager version of map could use an InputRange as input: template eagerMap(alias fun) { typeof(unaryFun!fun(ElementType!R.init))[] eagerMap(R)(R r) if (isInputRange!R && !isInfinite!R) { typeof(unaryFun!fun(ElementType!R.init))[] mapped; static if (hasLength!R) { mapped.length = r.length; foreach(i, elem; r) mapped[i] = unaryFun!fun(elem); } else { foreach(elem; r) mapped ~= unaryFun!fun(elem); // maybe with an ArrayAppender? } return mapped; } } 2. swapFront, swapBack, and swapAt methods // inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); They will be methods because they must be primitive operations of the respective ranges. However, there will be wrappers like this: // at module scope void swapFront(Range)(Range r1, Range r2) { static if (is(typeof(&(r1.front)) == ElementType!(Range)*)) { swap(r1.front, r2.front); } else { static assert(is(typeof(&(r1.swapFront)), "Cannot swap ranges"); r1.swapFront(r2); } } OK. I really like this possibility to test for members and activate them when possible. Maybe it could be abstracted away into a Select-like template? 3. sameFront() The gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. And if front does not return by ref? Do you then define the fronts to be different or compare the values? If front() does not return by ref, the range should define sameFront() as a member. If it doesn't, it won't have access to a number of algorithms. OK. So it's really sameFront and not equalFront or somesuch. About returning by ref, did you try to use 'auto ref'? I think I tried the day the feature appeared, without success, IIRC. Many ranges in Phobos return by ref and won't compose with other ranges because of that. Yah, auto ref was meant for that kind of work. But generally note that certain ranges actively refuse to return by ref in order to not expose addresses of their elements. Such ranges are fit for perfectly encapsulated containers. I was thinking of frustrating obstacles like: auto m = map!"a*a"([0,1,2,3]); auto c = cycle(m); // won't compile, m.front is not an lvalue, whereas c.front asks for one.Worse: string r = "ab"; auto c = cycle(r); // doesn't compile.
May 30 2010
On 05/30/2010 05:43 AM, Pelle wrote:string r = "ab"; auto c = cycle(r); // doesn't compile.That's a bug in Phobos. Andrei
May 30 2010
On 05/30/2010 05:11 AM, Philippe Sigaud wrote:On Sat, May 29, 2010 at 23:30, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>> wrote: On 05/29/2010 03:05 PM, Philippe Sigaud wrote: Does that mean that you changed some other parts recently? Not recently, I think I made the last changes before you joined the gang. OK, I wondered whether std.container had reverberations I wasn't aware of. Btw, I plan to play with trees and graphs and algorithms on them. I will modify my code to respect your container interface and see what sticks.Sounds great!1. First, I want to define this: // inside range type SomeRange property SomeRange save(); vote++ That should make it clear you need a forward range for an algorithm. Yah. Will the definition of a forward range change to incorporate save, or do you intend to distinguish ranges that can do R r2 = r1; and those that have: auto r2 = r1.save; ? Until now, they were one and the same to me.Yah. Essentially R r2 = r1; is not a guarantee that r2 is independent from r1. (The obvious case: R is a class or interface type.) You'll need to call R r2 = r1.save; to make sure that now you have two independently-moving ranges. So yes, save must be a member of all forward ranges. isForwardRange will yield false if it doesn't find save.The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them. I think many ranges and algorithm that you put in std have a constraint on InputRange that should be changed to ForwardRange. Most (all?) of the lazy ones should probably ask for a ForwardRange. Don't forget to update that part. I'm not sure about that. Could you give an example? Why would map() not work with an input range? Because you make a copy of the input range in Map's constructor? this(Range input) { _input = input; fillCache; } I supposed _input = input was not possible with an input range? It's the very definition of a forward range, no?An input range can be initialized from another input range, with the mention that it doesn't leave the original range independent from the copy. So Map works as it is.An eager version of map could use an InputRange as input: template eagerMap(alias fun) { typeof(unaryFun!fun(ElementType!R.init))[] eagerMap(R)(R r) if (isInputRange!R && !isInfinite!R) { typeof(unaryFun!fun(ElementType!R.init))[] mapped; static if (hasLength!R) { mapped.length = r.length; foreach(i, elem; r) mapped[i] = unaryFun!fun(elem); } else { foreach(elem; r) mapped ~= unaryFun!fun(elem); // maybe with an ArrayAppender? } return mapped; } }Lazy and input ranges are not incompatible.2. swapFront, swapBack, and swapAt methods // inside range type SomeRange void swapFront(ref ElementType!SomeRange obj); void swapBack(ref ElementType!SomeRange obj); void swapAt(size_t i, ref ElementType!SomeRange obj); They will be methods because they must be primitive operations of the respective ranges. However, there will be wrappers like this: // at module scope void swapFront(Range)(Range r1, Range r2) { static if (is(typeof(&(r1.front)) == ElementType!(Range)*)) { swap(r1.front, r2.front); } else { static assert(is(typeof(&(r1.swapFront)), "Cannot swap ranges"); r1.swapFront(r2); } } OK. I really like this possibility to test for members and activate them when possible. Maybe it could be abstracted away into a Select-like template?More detail please.3. sameFront() The gnarly bringToFront() algorithm needs the primitive: // inside range type SomeRange bool sameFront(SomeRange another); I think it's necessary for future algorithms as well. It's an optional primitive. In particular, if front() returns by reference it's easy to infer that two ranges have the same front by comparing the addresses of their front()s. And if front does not return by ref? Do you then define the fronts to be different or compare the values? If front() does not return by ref, the range should define sameFront() as a member. If it doesn't, it won't have access to a number of algorithms. OK. So it's really sameFront and not equalFront or somesuch.Yah. A previous attempt at naming was sameHead but I don't want to add too many notions.About returning by ref, did you try to use 'auto ref'? I think I tried the day the feature appeared, without success, IIRC. Many ranges in Phobos return by ref and won't compose with other ranges because of that. Yah, auto ref was meant for that kind of work. But generally note that certain ranges actively refuse to return by ref in order to not expose addresses of their elements. Such ranges are fit for perfectly encapsulated containers. I was thinking of frustrating obstacles like: auto m = map!"a*a"([0,1,2,3]); auto c = cycle(m); // won't compile, m.front is not an lvalue, whereas c.front asks for one. Putting auto ref front() {...} in Cycle does not change anything. Too bad.That's a bug in the compiler. Andrei
May 30 2010
On Sun, May 30, 2010 at 15:48, Andrei Alexandrescu < SeeWebsiteForEmail erdani.org> wrote:OK, understood.Will the definition of a forward range change to incorporate save, or do you intend to distinguish ranges that can do R r2 = r1; and those that have: auto r2 = r1.save; ? Until now, they were one and the same to me.Yah. Essentially R r2 = r1; is not a guarantee that r2 is independent from r1. (The obvious case: R is a class or interface type.) You'll need to call R r2 = r1.save; to make sure that now you have two independently-moving ranges. So yes, save must be a member of all forward ranges. isForwardRange will yield false if it doesn't find save.OK, so map(intputRange) could be affected after its creation by any modification to inputRange, that's coherent. I don't think I was ever confronted to this situation. I tend to discard ranges as I compose them.An input range can be initialized from another input range, with the mention that it doesn't leave the original range independent from the copy. So Map works as it is.Lazy and input ranges are not incompatible.I see that now. I really thought that a = b was a no-no for input ranges.It's just that my code is full of these static ifs. They are clear but I'd like two things: IfPossible!(someCode); // static if (is( typeof())) or __traits(compiles, someCode), then someCode. It's becoming a chore to repeat twice the same line. and: ConditionalCode!(cond1, code1, // static if cond1 then code1 cond2, code2, // else static if cond2 then code2 ... optionalDefaultCode); But I have no solution except by using q{} strings... Maybe with lazy void()?// at module scope void swapFront(Range)(Range r1, Range r2) { static if (is(typeof(&(r1.front)) == ElementType!(Range)*)) { swap(r1.front, r2.front); } else { static assert(is(typeof(&(r1.swapFront)), "Cannot swap ranges"); r1.swapFront(r2); } } OK. I really like this possibility to test for members and activate them when possible. Maybe it could be abstracted away into a Select-like template?More detail please.OK. So it's really sameFront and not equalFront or somesuch.Out of curiosity, what algorithm did you have in mind?Yah. A previous attempt at naming was sameHead but I don't want to add too many notions.I was thinking of frustrating obstacles like:I'll see if I can obtain a simple example and put it in bugzilla, then.auto m = map!"a*a"([0,1,2,3]); auto c = cycle(m); // won't compile, m.front is not an lvalue, whereas c.front asks for one. Putting auto ref front() {...} in Cycle does not change anything. Too bad.That's a bug in the compiler.
May 31 2010
On 05/31/2010 02:20 PM, Philippe Sigaud wrote:On Sun, May 30, 2010 at 15:48, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>> wrote: OK. So it's really sameFront and not equalFront or somesuch. Yah. A previous attempt at naming was sameHead but I don't want to add too many notions. Out of curiosity, what algorithm did you have in mind?bringToFront. Andrei
May 31 2010
Philippe Sigaud <philippe.sigaud gmail.com> wrote:I see a solution to this in template lambdas. mixin template ifPossible( alias code ) { static if ( __traits( compiles, code ) ) { mixin code; } } mixin ifPossible!( !{ fn( d ); } ); Where !{} is the syntax for an inline-specified template, i.e. a template lambda. Not sure it is worth adding to the language, but I have occasionally wanted it. -- SimenIt's just that my code is full of these static ifs. They are clear but I'd like two things: IfPossible!(someCode); // static if (is( typeof())) or __traits(compiles, someCode), then someCode. It's becoming a chore to repeat twice the same line. and: ConditionalCode!(cond1, code1, // static if cond1 then code1 cond2, code2, // else static if cond2 then code2 ... optionalDefaultCode); But I have no solution except by using q{} strings... Maybe with lazy void()?OK. I really like this possibility to test for members and activate them when possible. Maybe it could be abstracted away into a Select-like template?More detail please.
May 31 2010
On Sun, 30 May 2010 01:45:54 +0900, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I plan to make two more (hopefully the last two) changes to the range abstraction this morning. 1. First, I want to define this: // inside range type SomeRange property SomeRange save(); That simply returns a copy of the range. Most implementations look like this: // inside range type SomeRange property SomeRange save() { return this; } If SomeRange is a class or interface, you'd write: // inside range type SomeRange property SomeRange save() { return this->clone(); } The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them.I also want save. It allows you to treat some ranges in a unified way. Masahiro
May 29 2010
On Sat, 29 May 2010 12:45:54 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I plan to make two more (hopefully the last two) changes to the range abstraction this morning. 1. First, I want to define this: // inside range type SomeRange property SomeRange save(); That simply returns a copy of the range. Most implementations look like this: // inside range type SomeRange property SomeRange save() { return this; } If SomeRange is a class or interface, you'd write: // inside range type SomeRange property SomeRange save() { return this->clone(); } The idea is that save() provides a guaranteed means to take a snapshot in a range's state. The notable absents are input ranges - they are unable to define save(), and therefore some algorithms won't apply to them.In fact, I'd say you'd want to not define save unless it is the trivial case (your example for classes/interfaces shouldn't exist either). The reason being, ranges may be copied multiple times in algorithms, it's generally expected that the copying does not figure into the complexity of an algorithm. I assume that a non-trivial save will be expensive. Expect to see your algorithm blow up in runtime if save is not trivial. -Steve
Jun 01 2010