www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - challenge #2: implement the varargs_reduce metafunction

reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
My previous post defines max by availing itself of a metafunction called
varargs_reduce. Your challenge is to define it. varargs_reduce takes an
alias which it assumes is a function of two arguments, and it defines a
vararg function that applies that alias repeatedly, similarly to the
reduce primitive that is present in functional languages. Again, for
background on functional reduce, see e.g.:

http://www.joelonsoftware.com/items/2006/08/01.html

Define varargs_reduce to deduce the result type properly, and to 
generate code as efficient as possible.


Andrei
Jan 23 2007
next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:
 
 http://www.joelonsoftware.com/items/2006/08/01.html
 
 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.
 
 
 Andrei
Here is my try. I assume the reduce type must be exactly the same in all arguments. Should that not be the case? As for efficiency, I'm not sure what the requirements are. Is there to be any parallelization/vectorization considerations? This one, instead of linear iteration, uses binary recursion to calculate the result, but for *no particular reason* since its not actually any faster than linear recursion. This one seems much more easy that the fist challenge, so I suspect I'm missing something. ----------------------------------- //import stdext.stdio; import std.stdio : writeln = writefln; static import meta.Util; // Gets the typeof if not a type already. template TypeOf(alias ALIAS) { static if(is(ALIAS)) { alias ALIAS TypeOf; } else { alias typeof(ALIAS) TypeOf; } } // See if the types are the same template TypeEquals(alias A1, alias A2) { static if(is(TypeOf!(A1) == TypeOf!(A2))) const bool TypeEquals = true; else const bool TypeEquals = false; } // Duplicated template because primitive types do not match aliases :( template TypeEquals(T1, T2) { static if(is(T1 == T2)) const bool TypeEquals = true; else const bool TypeEquals = false; } /***************** varargsReduce ***************/ // Template implicit property workaround template varargsReduce(alias FN) { alias varargsReduce_Impl!(FN).varargsReduce_Impl varargsReduce; } template varargsReduce_Impl(alias FN) { static import std.traits; static assert(is(typeof(FN) == function) || is(typeof(FN) == delegate)); alias std.traits.ParameterTypeTuple!(FN) Params; alias std.traits.ReturnType!(FN) ReduceType; static assert(Params.length == 2); //static assert(typeof(FNarg1) == typeof(FNarg2)); static assert(TypeEquals!(Params[0], Params[1])); //static assert(typeof(FNarg1) == ReduceType); static assert(TypeEquals!(Params[0], ReduceType)); ReduceType varargsReduce_Impl(ARGS...) (ARGS args) { pragma(msg, "varargsReduce_Impl! length:" ~ meta.Util.itoa!(ARGS.length)); //static assert(ARGS.length != 0, "Error: no args"); static if(ARGS.length == 1) { static assert(TypeEquals!(typeof(args[0]), ReduceType)); return args[0]; } else static if(ARGS.length == 2) { static assert(TypeEquals!(typeof(args[0]), ReduceType)); static assert(TypeEquals!(typeof(args[1]), ReduceType)); return FN(args[0], args[1]); } else static if(ARGS.length >= 3) { ReduceType val1 = varargsReduce_Impl(args[0 .. ARGS.length/2]); ReduceType val2 = varargsReduce_Impl(args[ARGS.length/2 .. ARGS.length]); return FN(val1, val2); } else { static assert(false, "Error: no args"); } } } /***************** Usage ***************/ int sum(int a, int b) { return a + b; } char[] cat(char[] a, char[] b) { return a ~ b; } void main(char[][] args) { auto result = varargsReduce!(sum)(1, 3, 3, 7); writeln(result); auto result2 = varargsReduce!(cat)("H"[], "e"[], "l"[], "l"[], "o"[]); writeln(result2); } ----------------- itoa: toStrings an int -------------------- module meta.Util; template itoa(int i) { static if (i < 0) { static const char[] itoa = "-" ~ itoa!(-i); } else { static if (i / 10 > 0) { static const char[] itoa = itoa(i / 10) ~ "0123456789"[i % 10]; } else { static const char[] itoa = "0123456789"[i % 10 .. i % 10 + 1]; } } } -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jan 23 2007
next sibling parent reply "Christian Kamm" <kamm nospam.de> writes:
 This one seems much more easy that the fist challenge, so I suspect I'=
m =
 missing something.
Your implementation doesn't work with template functions. Here's a rathe= r = straightforward implementation that seems to work: template varargs_reduce(alias reduce2) { private: template ReduceType(ARGS...) { static assert(ARGS.length >=3D 2, "requires >=3D 2 arguments"); static if(ARGS.length =3D=3D 2) alias typeof(reduce2(ARGS[0], ARGS[1])) ReduceType; else static if(ARGS.length > 2) alias typeof(reduce2(ARGS[0], ReduceType!(ARGS[1..$]))) ReduceTyp= e; } public: // I'd have prefered a typeof(result) or typeof(return) as return typ= e = here. ReduceType!(ARGS) varargs_reduce(ARGS...)(ARGS args) { static assert(args.length >=3D 2, "requires >=3D 2 arguments"); = static if(args.length =3D=3D 2) auto result =3D reduce2(args[0], args[1]); else static if(args.length > 2) auto result =3D reduce2(args[0], varargs_reduce(args[1..$])); = return result; } } typeof(T+Q) plus(T,Q)(T a, Q b) { return a + b; } void main() { alias varargs_reduce!(plus).varargs_reduce plus_va; plus_va(1, 2.1, 3i, 4u); } While it compiles and seems to behave correctly, I'm suspicious of my us= e = of typeof in ReduceType - does the spec guarantee it does what I want? I= = tried using is and std.traits.ReturnType but typeof lead to the simplest= = solution. Cheers, Christian
Jan 23 2007
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Christian Kamm wrote:
 This one seems much more easy that the fist challenge, so I suspect 
 I'm missing something.
Your implementation doesn't work with template functions. Here's a rather straightforward implementation that seems to work: template varargs_reduce(alias reduce2) { private: template ReduceType(ARGS...) { static assert(ARGS.length >= 2, "requires >= 2 arguments"); static if(ARGS.length == 2) alias typeof(reduce2(ARGS[0], ARGS[1])) ReduceType; else static if(ARGS.length > 2) alias typeof(reduce2(ARGS[0], ReduceType!(ARGS[1..$]))) ReduceType; } public: // I'd have prefered a typeof(result) or typeof(return) as return type here. ReduceType!(ARGS) varargs_reduce(ARGS...)(ARGS args) { static assert(args.length >= 2, "requires >= 2 arguments"); static if(args.length == 2) auto result = reduce2(args[0], args[1]); else static if(args.length > 2) auto result = reduce2(args[0], varargs_reduce(args[1..$])); return result; } } typeof(T+Q) plus(T,Q)(T a, Q b) { return a + b; } void main() { alias varargs_reduce!(plus).varargs_reduce plus_va; plus_va(1, 2.1, 3i, 4u); } While it compiles and seems to behave correctly, I'm suspicious of my use of typeof in ReduceType - does the spec guarantee it does what I want? I tried using is and std.traits.ReturnType but typeof lead to the simplest solution.
This is a good solution. Ideally, you would combine it with Bruno Medeiros' idea to expand the template in a binary manner so as to spread dependencies. One subtle point is that you don't want to use auto result = ...; We have not yet defined the semantics of transporting storage class entirely. It is possible that for backward compatibility reasons, auto will eliminate the 'inout' storage class that max2 so laboriously transported out. If that happens, your code will not compile. So the best bet is to simply return the reduce2 expression directly. (Now I guess it's self-evident why I said earlier that D's handling of storage classes is a source of radioactive waste in the type system. It insidiously hits everywhere and when least expected.) Great work, thanks! Andrei
Jan 23 2007
prev sibling next sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Bruno Medeiros wrote:
 This one seems much more easy that the fist challenge, so I suspect I'm 
 missing something.
I suspect the same since I have just a few lines. This is what I came up with, does it meet the requirements? There must be something wrong. template reduce(alias fn) { typeof(T[0]) reduce(T...)(T args) { static assert( is(T[0] == T[1]) && is( T[0] == typeof( fn(T[0], T[1]) ) ), "try again" ); static assert( args.length > 1, "nothing to reduce here"); auto result = args[0]; foreach(arg; args[1..$]) result = fn(result, arg); return result; } } example: alias reduce!(function(int a, int b) { return (a > b) ? a : b; }) max; assert( max(2,55,787) == 787 );
Jan 23 2007
parent reply janderson <askme me.com> writes:
Lutger wrote:
 Bruno Medeiros wrote:
 This one seems much more easy that the fist challenge, so I suspect 
 I'm missing something.
I suspect the same since I have just a few lines. This is what I came up with, does it meet the requirements? There must be something wrong. template reduce(alias fn) { typeof(T[0]) reduce(T...)(T args) { static assert( is(T[0] == T[1]) && is( T[0] == typeof( fn(T[0], T[1]) ) ), "try again" ); static assert( args.length > 1, "nothing to reduce here"); auto result = args[0]; foreach(arg; args[1..$]) result = fn(result, arg); return result; } } example: alias reduce!(function(int a, int b) { return (a > b) ? a : b; }) max; assert( max(2,55,787) == 787 );
What about maxtype support?
Jan 23 2007
parent Lutger <lutger.blijdestijn gmail.com> writes:
janderson wrote:
 What about maxtype support?
I see, it doesn't do that, not good. In fact it demands all arguments and return value to be of the same type.
Jan 23 2007
prev sibling parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Bruno Medeiros wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.


 Andrei
Here is my try. I assume the reduce type must be exactly the same in all arguments. Should that not be the case?
That is not the case, and is actually an important point in implementing varargs_reduce.
 As for efficiency, I'm not sure what the requirements are. Is there to 
 be any parallelization/vectorization considerations? This one, instead 
 of linear iteration, uses binary recursion to calculate the result, but 
 for *no particular reason* since its not actually any faster than linear 
 recursion.
It could well be faster. The dependency chain in your expansion has logarithmic depth; in linear expansion it has linear depth. Superscalar machines have the ability to execute independent operations literally in parallel (they have multiple ALUs).
 This one seems much more easy that the fist challenge, so I suspect I'm 
 missing something.
As Christian Kamm mentioned, your solution does not work with templates, and min2 actually is one. Andrei
Jan 23 2007
prev sibling next sibling parent reply janderson <askme me.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:
 
 http://www.joelonsoftware.com/items/2006/08/01.html
 
 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.
 
 
 Andrei
While your at it. What about a multi-threaded version? ie Each function that is called is placed on one of 4 threads. -Joel
Jan 23 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
janderson wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.


 Andrei
While your at it. What about a multi-threaded version? ie Each function that is called is placed on one of 4 threads.
That's a good point, and goes right to the core of my solution, which (similarly to Bruno Medeiros') arranges operations in an optimal way for superscalar evaluation, e.g. max(a, b, c, d) is expanded not to: max2(max2(max2(a, b), c), d) but instead to: max2(max2(a, b), max2(c, d)) The number of operations is the same but in the latter case there is logaritmically less dependency between partial results. When max2 expands into a primitive comparison, the code is easy prey for a superscalar machine to execute in parallel. This won't count in a great deal of real code, but the fact that this will go in the standard library warrants the thought. Besides, the fact that the language makes it so easy, we can actually think of such subtlety, is very encouraging. Andrei
Jan 23 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 janderson wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.


 Andrei
While your at it. What about a multi-threaded version? ie Each function that is called is placed on one of 4 threads.
That's a good point, and goes right to the core of my solution, which (similarly to Bruno Medeiros') arranges operations in an optimal way for superscalar evaluation, e.g. max(a, b, c, d) is expanded not to: max2(max2(max2(a, b), c), d) but instead to: max2(max2(a, b), max2(c, d))
Assumes the operation is associative. That may not always be the case; it's not even necessarily true that the function takes arguments of identical types. Silly example: char[] accumulate(char[] arr, char element) { return arr ~= element; } Of course, you could *require* that the operation is associative, but I didn't see that anywhere in the specification. And as far as I can tell, that generally isn't assumed for reduce. For instance in Lisp, REDUCE is either left-to-right or right-to-left (depending on a flag)[1]. In Python, it's always left-to-right[2]. (those were the ones I could most easily find with a quick Google search) [1]: http://www.lisp.org/HyperSpec/Body/fun_reduce.html [2]: http://docs.python.org/lib/built-in-funcs.html
Jan 23 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Frits van Bommel wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 janderson wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction 
 called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.


 Andrei
While your at it. What about a multi-threaded version? ie Each function that is called is placed on one of 4 threads.
That's a good point, and goes right to the core of my solution, which (similarly to Bruno Medeiros') arranges operations in an optimal way for superscalar evaluation, e.g. max(a, b, c, d) is expanded not to: max2(max2(max2(a, b), c), d) but instead to: max2(max2(a, b), max2(c, d))
Assumes the operation is associative. That may not always be the case; it's not even necessarily true that the function takes arguments of identical types. Silly example: char[] accumulate(char[] arr, char element) { return arr ~= element; }
I think this ia a good point. Probably varargs_reduce must be linear to make the most conservative assumptions. There is always the chance of defining varargs_reduce_parallel. Thanks! Andrei
Jan 23 2007
next sibling parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Frits van Bommel wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 janderson wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 My previous post defines max by availing itself of a metafunction 
 called
 varargs_reduce. Your challenge is to define it. varargs_reduce 
 takes an
 alias which it assumes is a function of two arguments, and it 
 defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to 
 generate code as efficient as possible.


 Andrei
While your at it. What about a multi-threaded version? ie Each function that is called is placed on one of 4 threads.
That's a good point, and goes right to the core of my solution, which (similarly to Bruno Medeiros') arranges operations in an optimal way for superscalar evaluation, e.g. max(a, b, c, d) is expanded not to: max2(max2(max2(a, b), c), d) but instead to: max2(max2(a, b), max2(c, d))
Assumes the operation is associative. That may not always be the case; it's not even necessarily true that the function takes arguments of identical types. Silly example: char[] accumulate(char[] arr, char element) { return arr ~= element; }
I think this ia a good point. Probably varargs_reduce must be linear to make the most conservative assumptions. There is always the chance of defining varargs_reduce_parallel. Thanks! Andrei
I think that would be best (having a semi-seperate _parellel version). I also wonder about applying this same "feature" to functions of 3 or 4 arguments (or even arbitrary! so long as none are themselves variadic), but that could come later. -- Chris Nicholson-Sauls
Jan 23 2007
prev sibling parent janderson <askme me.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:

 
 I think this ia a good point. Probably varargs_reduce must be linear to 
 make the most conservative assumptions. There is always the chance of 
 defining varargs_reduce_parallel. Thanks!
 
 
 Andrei
My thought exactly. It would be nice to have a vargags_reduce and simply add _parallel to it for instant speed gains. I also think that there will be other functions that could go the same way. Perhaps _parallel (or whatever we decide to call it) should be standardized.
Jan 24 2007
prev sibling next sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website for Email)
(SeeWebsiteForEmail erdani.org)'s article
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to
 generate code as efficient as possible.

 Andrei
There is a really simple solution, but I'm not sure if it does the right thing with respect to your wording above. What exactly is meant by deducing the result type properly? Specifically, if the input types are different kinds of objects, is the result expected to have the same type as a linear sequence of operations? 1. Can we assume the type conversions are non-cyclic? 2. Should we assume that the delegate looks like: T dg(T, T), or like: T dg(U, V) 3. If the signature is T dg(U, V), can we assume that type conversions are non-cyclic? Cyclic types: (requires more template logic than actually shown here) Rock add(Paper x, Scissors y); Paper add(Scissors x, Rock x); Scissors add(Rock x, Paper y); If this kind of stuff is permitted, the solution is possible but a lot messier. // Simple solution: import std.stdio; import std.traits; template reduce(D, E...) { ReturnType!(D) reduce(D dg, E v) { ReturnType!(D) rv; static if (E.length >= 1) { rv = v[0]; foreach(i, a; v[1..$]) { rv = dg(rv, a); } } return rv; } } int main(char[][] args) { int add(int a, int b) { return a + b; } double mul(double a, double b) { return a * b; } int x = reduce(& add, 101, 102, 103, 104); double y = reduce(& mul, .1, .2, .3, .4, .5); writefln("sum=%s prod=%s", x, y); return 0; }
Jan 23 2007
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website for Email)
 (SeeWebsiteForEmail erdani.org)'s article
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to
 generate code as efficient as possible.

 Andrei
There is a really simple solution, but I'm not sure if it does the right thing with respect to your wording above. What exactly is meant by deducing the result type properly? Specifically, if the input types are different kinds of objects, is the result expected to have the same type as a linear sequence of operations? 1. Can we assume the type conversions are non-cyclic? 2. Should we assume that the delegate looks like: T dg(T, T), or like: T dg(U, V) 3. If the signature is T dg(U, V), can we assume that type conversions are non-cyclic? Cyclic types: (requires more template logic than actually shown here) Rock add(Paper x, Scissors y); Paper add(Scissors x, Rock x); Scissors add(Rock x, Paper y); If this kind of stuff is permitted, the solution is possible but a lot messier.
Interesting point. I think varargs_reduce can safely assume that linear reduction always works f(f(f(f(...f(a1, a2), a3), a4), ...). My solution optimizes more aggressively by assuming that reduction can be performed in any order. Your solution is less optimal because it uses a delegate. Ideally you just use an alias and deduce the result type by simply using the alias. Andrei
Jan 23 2007
prev sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Andrei Alexandrescu (See Website for Email)" 
<SeeWebsiteForEmail erdani.org> wrote in message 
news:ep4i7p$2n1t$4 digitaldaemon.com...
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to generate 
 code as efficient as possible.


 Andrei
This post is not meant to be inflammatory. Answer me this: are you just toying with us, tempting us with things which are unimplementable without new features which are secretly being developed for D? Or are you just surreptitiously trying to get the community to write a bunch of standard library metacode for you? ;) How about this: start a thread in which you disclose all the fancy features you (you plural, you and Walter, since you now seem to be the _team_ designing D) have been planning. For that matter, is coming up with crazy new features like inout returns and opImplicitCast _all_ Walter's doing? Or is he fixing bugs?
Jan 23 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Jarrett Billingsley wrote:
 "Andrei Alexandrescu (See Website for Email)" 
 <SeeWebsiteForEmail erdani.org> wrote in message 
 news:ep4i7p$2n1t$4 digitaldaemon.com...
 My previous post defines max by availing itself of a metafunction called
 varargs_reduce. Your challenge is to define it. varargs_reduce takes an
 alias which it assumes is a function of two arguments, and it defines a
 vararg function that applies that alias repeatedly, similarly to the
 reduce primitive that is present in functional languages. Again, for
 background on functional reduce, see e.g.:

 http://www.joelonsoftware.com/items/2006/08/01.html

 Define varargs_reduce to deduce the result type properly, and to generate 
 code as efficient as possible.


 Andrei
This post is not meant to be inflammatory.
Definitely not taken as such.
 Answer me this: are you just toying with us, tempting us with things which 
 are unimplementable without new features which are secretly being developed 
 for D?  Or are you just surreptitiously trying to get the community to write 
 a bunch of standard library metacode for you?  ;)
It's really easy. I can't right now disclose all of my agenda for objective reasons (I will do it later), but the gist of the matter is simple: 1. There are some simple tests for a language gaging very precisely the power of that language 2. I want D to pass those tests 3. I want to increase community's awareness of the importance of those tests for larger use cases, as well as gather ideas from the community about how to overcome D's shortcomings in passing the tests.
 How about this: start a thread in which you disclose all the fancy features 
 you (you plural, you and Walter, since you now seem to be the _team_ 
 designing D) have been planning.
We are doing it already, e.g. by describing my suggested max implementation. But I don't want to do that preemptively, because I'd bias future responses. I wished, for example, that somebody could suggest a better design instead of "storageof(T) T".
 For that matter, is coming up with crazy new features like inout returns and 
 opImplicitCast _all_ Walter's doing?  Or is he fixing bugs? 
Walter and I have been discussing these issues and we have agreement on the necessity of certain features (of which indeed better inout handling and opImplicitCast are two). At this point, we don't have much more of a design than what I've already shown. For all I know, Walter hasn't started implementing any of them. Andrei
Jan 23 2007
parent reply kris <foo bar.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
[snip]

 It's really easy. I can't right now disclose all of my agenda for 
 objective reasons (I will do it later), but the gist of the matter is 
 simple:
 
 1. There are some simple tests for a language gaging very precisely the 
 power of that language
 
 2. I want D to pass those tests
 
 3. I want to increase community's awareness of the importance of those 
 tests for larger use cases, as well as gather ideas from the community 
 about how to overcome D's shortcomings in passing the tests.
That's great, Andrei. And I, for one, truly want to see some of those changes happen (such as read-only arrays, covered by the const you talk of). However, there are also some rather pressing matters of fixing stuff that is simply broken, and is hampering attempts to build real, live, useful apps at this time. Yeah, new features are sexy cool, and fixing long-standing issues ain't. How about taking a long hard look at what sucks in D right now, and making those a primary priority? Having read many of your posts since yesterday, it seems you've identified a number of those. Great! Yet, you guys are apparently pressing ahead with wonderful new stuff instead? Like, for instance, an updated GC? When did that ever top the list of real-world issues for D? To anticipate a question: There's some serious issues with templates in libs (you really can't put them there successfully), some serious issues with debuggers not being able to locate source-files for templates; lots of problems related to real-world, end-user, /usability/ of the language on a daily basis. I submit that such issues are just as important a test to pass than the ones you allude to. Dare I say, perhaps more so for people actually using the language right now? Regards; - Kris
Jan 23 2007
next sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 [snip]
 
 It's really easy. I can't right now disclose all of my agenda for 
 objective reasons (I will do it later), but the gist of the matter is 
 simple:

 1. There are some simple tests for a language gaging very precisely 
 the power of that language

 2. I want D to pass those tests

 3. I want to increase community's awareness of the importance of those 
 tests for larger use cases, as well as gather ideas from the community 
 about how to overcome D's shortcomings in passing the tests.
That's great, Andrei. And I, for one, truly want to see some of those changes happen (such as read-only arrays, covered by the const you talk of). However, there are also some rather pressing matters of fixing stuff that is simply broken, and is hampering attempts to build real, live, useful apps at this time. Yeah, new features are sexy cool, and fixing long-standing issues ain't. How about taking a long hard look at what sucks in D right now, and making those a primary priority? Having read many of your posts since yesterday, it seems you've identified a number of those. Great! Yet, you guys are apparently pressing ahead with wonderful new stuff instead? Like, for instance, an updated GC? When did that ever top the list of real-world issues for D?
I'm afraid I am not in the position to help here. All I'm really doin' is bitchin'. Walter does the actual work, and prioritizing and implementing is entirely his charter. That being said, my view on what should be fixed in D is a bit different. For example, as I said, one of my main foci is putting a lead sarcophagus around "inout" before its radioactive waste affects too much code that will become broken later. Also, implicit conversion rules will change, and again I want to get that in ASAP so not too much code suffers. And so on.
 To anticipate a question:
 
 There's some serious issues with templates in libs (you really can't put 
 them there successfully), some serious issues with debuggers not being 
 able to locate source-files for templates; lots of problems related to 
 real-world, end-user, /usability/ of the language on a daily basis.
 
 I submit that such issues are just as important a test to pass than the 
 ones you allude to. Dare I say, perhaps more so for people actually 
 using the language right now?
I entirely agree. Figuring out priorities is Walter's call. Let's keep our fingers crossed :o). Andrei
Jan 23 2007
next sibling parent reply kris <foo bar.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 
 Andrei Alexandrescu (See Website For Email) wrote:
 [snip]

 It's really easy. I can't right now disclose all of my agenda for 
 objective reasons (I will do it later), but the gist of the matter is 
 simple:

 1. There are some simple tests for a language gaging very precisely 
 the power of that language

 2. I want D to pass those tests

 3. I want to increase community's awareness of the importance of 
 those tests for larger use cases, as well as gather ideas from the 
 community about how to overcome D's shortcomings in passing the tests.
That's great, Andrei. And I, for one, truly want to see some of those changes happen (such as read-only arrays, covered by the const you talk of). However, there are also some rather pressing matters of fixing stuff that is simply broken, and is hampering attempts to build real, live, useful apps at this time. Yeah, new features are sexy cool, and fixing long-standing issues ain't. How about taking a long hard look at what sucks in D right now, and making those a primary priority? Having read many of your posts since yesterday, it seems you've identified a number of those. Great! Yet, you guys are apparently pressing ahead with wonderful new stuff instead? Like, for instance, an updated GC? When did that ever top the list of real-world issues for D?
I'm afraid I am not in the position to help here. All I'm really doin' is bitchin'.
Drat ... that's apparently all I'm achieving too :(
 Walter does the actual work, and prioritizing and 
 implementing is entirely his charter.
 
 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a lead 
 sarcophagus around "inout" before its radioactive waste affects too much 
 code that will become broken later. Also, implicit conversion rules will 
 change, and again I want to get that in ASAP so not too much code 
 suffers. And so on.
Aye, and that is certainly appreciated. Some of the items you've mentioned recently have long been festering sores for many. It'll certainly be refreshing to see some attention focused upon those. Regarding implicit conversion rules -- are you including the "issues" currently surrounding signature-matching when using constants?
 
 To anticipate a question:

 There's some serious issues with templates in libs (you really can't 
 put them there successfully), some serious issues with debuggers not 
 being able to locate source-files for templates; lots of problems 
 related to real-world, end-user, /usability/ of the language on a 
 daily basis.

 I submit that such issues are just as important a test to pass than 
 the ones you allude to. Dare I say, perhaps more so for people 
 actually using the language right now?
I entirely agree. Figuring out priorities is Walter's call. Let's keep our fingers crossed :o).
Hehe; superstition :-p Well, thanks for replying. The clarification is much appreciated, though I'm honestly sad to hear you're not more deeply involved. Cheers; - Kris
Jan 23 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a 
 lead sarcophagus around "inout" before its radioactive waste affects 
 too much code that will become broken later. Also, implicit conversion 
 rules will change, and again I want to get that in ASAP so not too 
 much code suffers. And so on.
Aye, and that is certainly appreciated. Some of the items you've mentioned recently have long been festering sores for many. It'll certainly be refreshing to see some attention focused upon those. Regarding implicit conversion rules -- are you including the "issues" currently surrounding signature-matching when using constants?
Could you give more detail? Andrei
Jan 24 2007
next sibling parent reply kris <foo bar.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 
 Andrei Alexandrescu (See Website For Email) wrote:

 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a 
 lead sarcophagus around "inout" before its radioactive waste affects 
 too much code that will become broken later. Also, implicit 
 conversion rules will change, and again I want to get that in ASAP so 
 not too much code suffers. And so on.
Aye, and that is certainly appreciated. Some of the items you've mentioned recently have long been festering sores for many. It'll certainly be refreshing to see some attention focused upon those. Regarding implicit conversion rules -- are you including the "issues" currently surrounding signature-matching when using constants?
Could you give more detail? Andrei
Yep -- will get back to you with examples (probably tomorrow)
Jan 24 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:

 Andrei Alexandrescu (See Website For Email) wrote:

 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a 
 lead sarcophagus around "inout" before its radioactive waste affects 
 too much code that will become broken later. Also, implicit 
 conversion rules will change, and again I want to get that in ASAP 
 so not too much code suffers. And so on.
Aye, and that is certainly appreciated. Some of the items you've mentioned recently have long been festering sores for many. It'll certainly be refreshing to see some attention focused upon those. Regarding implicit conversion rules -- are you including the "issues" currently surrounding signature-matching when using constants?
Could you give more detail? Andrei
Yep -- will get back to you with examples (probably tomorrow)
Well there is this issue that &foo where foo is an overloaded will give you pointer to the first version of foo in the source file. There's no way to disambiguate as far as I know. Particularly frustrating for property methods void prop(float x) { ... } float prop() { ... } &prop just gives you the first one no matter what. Maybe not what you're talking about with "matching using constants" but it is related to signature matching (or lack thereof). --bb
Jan 24 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 Well there is this issue that &foo where foo is an overloaded will give 
 you pointer to the first version of foo in the source file.  There's no 
 way to disambiguate as far as I know.  Particularly frustrating for 
 property methods
 
    void prop(float x) { ... }
    float prop() { ... }
 
 
 &prop just gives you the first one no matter what.
 
 Maybe not what you're talking about with "matching using constants" but 
 it is related to signature matching (or lack thereof).
Ouch. This warrants a bug report. It sure has ripples in lots of template code, too. Andrei
Jan 24 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Bill Baxter wrote:
 Well there is this issue that &foo where foo is an overloaded will 
 give you pointer to the first version of foo in the source file.  
 There's no way to disambiguate as far as I know.  Particularly 
 frustrating for property methods

    void prop(float x) { ... }
    float prop() { ... }


 &prop just gives you the first one no matter what.

 Maybe not what you're talking about with "matching using constants" 
 but it is related to signature matching (or lack thereof).
Ouch. This warrants a bug report. It sure has ripples in lots of template code, too. Andrei
It's in there. http://d.puremagic.com/issues/show_bug.cgi?id=52 --bb
Jan 25 2007
prev sibling parent reply John Reimer <terminal.node gmail.com> writes:
On Wed, 24 Jan 2007 23:12:21 -0800, Andrei Alexandrescu (See Website For
Email) wrote:

 Bill Baxter wrote:
 Well there is this issue that &foo where foo is an overloaded will give 
 you pointer to the first version of foo in the source file.  There's no 
 way to disambiguate as far as I know.  Particularly frustrating for 
 property methods
 
    void prop(float x) { ... }
    float prop() { ... }
 
 
 &prop just gives you the first one no matter what.
 
 Maybe not what you're talking about with "matching using constants" but 
 it is related to signature matching (or lack thereof).
Ouch. This warrants a bug report. It sure has ripples in lots of template code, too. Andrei
That's not considered a bug, however. There have been many complaints about D's signature lookup "protocol" over the last 2 to 3 years. Walter has many times stated that it's done that way on purpose, I think to avoid compiler complexity. Maybe this example shows yet another reason why it hurts, but we've had no success convincing him otherwise. That's just one example of "silent acception" of the symbol. I'm actually surprised that Walter didn't invalidate that bug report. I guess this is an example of the slow aggregation of evidence against certain language/implementation mechanisms. Template issues related to this may further this realization. -JJR
Jan 25 2007
parent reply Walter Bright <newshound digitalmars.com> writes:
John Reimer wrote:
 On Wed, 24 Jan 2007 23:12:21 -0800, Andrei Alexandrescu (See Website For
 Email) wrote:
 
 Bill Baxter wrote:
 Well there is this issue that &foo where foo is an overloaded will give 
 you pointer to the first version of foo in the source file.  There's no 
 way to disambiguate as far as I know.  Particularly frustrating for 
 property methods

    void prop(float x) { ... }
    float prop() { ... }


 &prop just gives you the first one no matter what.

 Maybe not what you're talking about with "matching using constants" but 
 it is related to signature matching (or lack thereof).
Ouch. This warrants a bug report. It sure has ripples in lots of template code, too. Andrei
That's not considered a bug, however. There have been many complaints about D's signature lookup "protocol" over the last 2 to 3 years. Walter has many times stated that it's done that way on purpose, I think to avoid compiler complexity. Maybe this example shows yet another reason why it hurts, but we've had no success convincing him otherwise. That's just one example of "silent acception" of the symbol. I'm actually surprised that Walter didn't invalidate that bug report.
I didn't invalidate it because it is a bug. The lookup thing was a debate about whether Java-style or C++-style overriding is done. D does C++ style overriding.
Jan 25 2007
parent reply BCS <ao pathlink.com> writes:
Reply to Walter,

 I didn't invalidate it because it is a bug. The lookup thing was a
 debate about whether Java-style or C++-style overriding is done. D
 does C++ style overriding.
 
Is it? VS complains if there is any question, I haven't checked GCC. Or are you talking about something different.
Jan 26 2007
parent reply Walter Bright <newshound digitalmars.com> writes:
BCS wrote:
 Reply to Walter,
 
 I didn't invalidate it because it is a bug. The lookup thing was a
 debate about whether Java-style or C++-style overriding is done. D
 does C++ style overriding.
Is it? VS complains if there is any question, I haven't checked GCC. Or are you talking about something different.
Looks like something different.
Jan 26 2007
parent reply James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 BCS wrote:
 Reply to Walter,

 I didn't invalidate it because it is a bug. The lookup thing was a
 debate about whether Java-style or C++-style overriding is done. D
 does C++ style overriding.
Is it? VS complains if there is any question, I haven't checked GCC. Or are you talking about something different.
Looks like something different.
Could you maybe explain what you mean by "C++-style overriding" as opposed to Java-style overriding? In what way is D more like C++ here than like Java? The overriding rules seem similar in C++ and in Java, though the name/function lookup rules are very different, and there are restrictions on Java related to changing visibility when overriding. -- James
Jan 26 2007
parent reply Walter Bright <newshound digitalmars.com> writes:
James Dennett wrote:
 Could you maybe explain what you mean by "C++-style
 overriding" as opposed to Java-style overriding?  In
 what way is D more like C++ here than like Java?
In C++, overriding one function in a base class prevents use of any of the base class function's overloads. In Java, you can override one overload and inherit the rest.
Jan 26 2007
parent James Dennett <jdennett acm.org> writes:
Walter Bright wrote:
 James Dennett wrote:
 Could you maybe explain what you mean by "C++-style
 overriding" as opposed to Java-style overriding?  In
 what way is D more like C++ here than like Java?
In C++, overriding one function in a base class prevents use of any of the base class function's overloads. In Java, you can override one overload and inherit the rest.
OK; I'd say this is a name lookup difference, not a difference in how overriding is done. In C++ it doesn't prevent the use of the base class function's overloads: they can still be used if you name their scope, or if you bring them into the scope of the derived class with a using declaration. (It certainly does prevent certain idiomatic uses of those base class functions unless you add a using declaration. It turns out to generally be a bad idea to overload virtual functions for design reasons unrelated to the specifics of a particular language though, so this isn't much of an issue in practice.) In Java, lookup is done by signature, not just name, and there's no such thing as "hiding" of a name in a base class by a name in a derived class. Both systems have their pros and cons, and D could reasonably resemble either, so long as the choice "feels" consistent with other decisions in the design of D. -- James
Jan 26 2007
prev sibling parent reply kris <foo bar.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 
 Andrei Alexandrescu (See Website For Email) wrote:

 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a 
 lead sarcophagus around "inout" before its radioactive waste affects 
 too much code that will become broken later. Also, implicit 
 conversion rules will change, and again I want to get that in ASAP so 
 not too much code suffers. And so on.
Aye, and that is certainly appreciated. Some of the items you've mentioned recently have long been festering sores for many. It'll certainly be refreshing to see some attention focused upon those. Regarding implicit conversion rules -- are you including the "issues" currently surrounding signature-matching when using constants?
Could you give more detail? Andrei
Please pardon the delay, and the length of this post ... here's a test: extern (C) int printf (char*, ...); class Foo { void write (int x) {printf("int\n");} void write (uint x) {printf("uint\n");} void write (long x) {printf("long\n");} void write (char x) {printf("char\n");} void write (wchar x) {printf("wchar\n");} void write (double x) {printf("double\n");} void write (char[] x) {printf("char[]\n");} void write (wchar[] x) {printf("wchar[]\n");} } void main() { auto foo = new Foo; foo.write ('c'); foo.write (1); foo.write (1u); foo.write (3.14); //foo.write ("asa"); } prints: char int uint double DMD has actually become smarter than the last time I tried something like this: it manages to select the correct overload for 'c' whereas before it couldn't decide whether int or uint was a better match for the char instead. This is good. It seems clear from the above that D /defaults/ the type of character, and undecorated integers, to something appropriate? In the above case 'c' is defaulted to char, rather than wchar, for example. The undecorated int constant is defaulted to int, rather than uint or long. This is good. Now for the broken part. When you uncomment the string constant, the compiler gets all confused about whether it's a char[] or wchar[]. There is no defaulting to one type, as there is for other constants (such as char). It /is/ possible to decorate the string constant in a similar manner to decorating integer constants: foo.write ("qwe"c); And this, of course, compiles. It's a PITA though, and differs from the rules for other constants. Things start to go south when using templates with string constants. For example, take this template sig: uint locate(T) (T[] source, T match, uint start=0) This is intended to handle types of char[], wchar[] and dchar[]. There's a uint on the end, as opposed to an int. Suppose I call it like this: locate ("abc", "ab", 1); we get a compile error, since the int-constant does not match a uint in the sig (IFTI currently needs exact sig matches). In order to get around this, we wrap the template with a few functions: uint locate (char[] source, char[] match, uint start=0) { return locateT!(char) (source, match, start); } uint locate (wchar[] source, wchar[] match, uint start=0) { return locateT!(wchar) (source, match, start); } and dchar too. Now we call it: locate ("abc", "ab", 1); Well, the int/uint error goes away (since function matching operates differently than IFTI matching), but we've now got our old friend back again -- the constant char[], wchar[], dchar[] mismatch problem. How about another type of template? Here's one that does some simply text processing: T[] layout(T) (T[] output, T[] format, T[][] subs...) This has an output buffer, a format string, and a set of optional args; all of the same type. If I call it like so: char[128] tmp; char[] world = "world"; layout (tmp, "hello %1", world); that compiles ok. If I use wchar[] instead, it doesn't compile: wchar[128] tmp; wchar[] world = "world"; layout (tmp, "hello %1", world); In this case, the constant string used for formatting remains as a char[], so the template match fails (args: wchar[], char[], wchar[]) However, if I change the template signature to this instead: T[] layout(T) (T[] output, T[][] subs...) then everything works the way I want it to, but the design is actually wrong (the format string is now not required). String constants can be a royal PITA. inout ----- Since you're working on inout also, I'd like to ask what the plan is relating to a couple of examples. Tango uses this style of call quite regularly: get (inout int x); get (inout char[] x); etc. This is a clean way to pass-by-reference, instead of dealing with all the pointer syntax. I sure hope D will retain reference semantics like this, in some form? One current problem with inout, which you might not be aware of, is with regard to const structs. I need to pass structs by reference, because I don't want to pass them by value. Applying inout is the mechanism for describing this: struct Bar {int a, b;} Bar b = {1, 2}; void parf (inout Bar x) {} void main() { parf (b); } That all works fine. However, when I want to make those structs /const/ instead, I cannot use inout since it has mutating semantics: I get a compile error to that effect: const Bar b = {1, 2};
 Error: cannot modify const variable 'b'
That is, there's no supported way to pass a const struct by reference. The response from Walter in the past has been "just use a pointer instead" ... well, yes I could do that. But it appears to be indicative of a problem with the language design? Why do I want to use const? Well, the data held therein is for reference only, and (via some future D vendor) I want that reference data placed into a ROM segment. I do a lot of work with MCUs, and this sort of thing is a common requirement. Cheers; - Kris
Jan 25 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
kris wrote:
[about implicit conversion rules]
 extern (C) int printf (char*, ...);
 
 class Foo
 {
         void write (int x) {printf("int\n");}
         void write (uint x) {printf("uint\n");}
         void write (long x) {printf("long\n");}
         void write (char x) {printf("char\n");}
         void write (wchar x) {printf("wchar\n");}
         void write (double x) {printf("double\n");}
 
         void write (char[] x) {printf("char[]\n");}
         void write (wchar[] x) {printf("wchar[]\n");}
 }
 
 void main()
 {
         auto foo = new Foo;
 
         foo.write ('c');
         foo.write (1);
         foo.write (1u);
         foo.write (3.14);
         //foo.write ("asa");
 }
 
 prints:
 
 char
 int
 uint
 double
 
 
 DMD has actually become smarter than the last time I tried something 
 like this: it manages to select the correct overload for 'c' whereas 
 before it couldn't decide whether int or uint was a better match for the 
 char instead. This is good.
 It seems clear from the above that D /defaults/ the type of character, 
 and undecorated integers, to something appropriate? In the above case 
 'c' is defaulted to char, rather than wchar, for example. The 
 undecorated int constant is defaulted to int, rather than uint or long. 
 This is good.
Yup. So far so good.
 Now for the broken part. When you uncomment the string constant, the 
 compiler gets all confused about whether it's a char[] or wchar[]. There 
 is no defaulting to one type, as there is for other constants (such as 
 char). It /is/ possible to decorate the string constant in a similar 
 manner to decorating integer constants:
 
 foo.write ("qwe"c);
 
 And this, of course, compiles. It's a PITA though, and differs from the 
 rules for other constants.
I talked to Walter about this and it's not a bug, it's a feature :o). Basically it's hard to decide what to do with an unadorned string when both wchar[] and char[] would want to "attract" it. I understand you're leaning towards defaulting to char[]? Then probably others will be unhappy.
 Things start to go south when using templates 
 with string constants. For example, take this template sig:
 
 uint locate(T) (T[] source, T match, uint start=0)
 
 This is intended to handle types of char[], wchar[] and dchar[]. There's 
 a uint on the end, as opposed to an int. Suppose I call it like this:
 
 locate ("abc", "ab", 1);
 
 we get a compile error, since the int-constant does not match a uint in 
 the sig (IFTI currently needs exact sig matches). In order to get around 
 this, we wrap the template with a few functions:
 
 uint locate (char[] source, char[] match, uint start=0)
 {
     return locateT!(char) (source, match, start);
 }
 
 uint locate (wchar[] source, wchar[] match, uint start=0)
 {
     return locateT!(wchar) (source, match, start);
 }
 
 and dchar too.
 
 Now we call it:
 
 locate ("abc", "ab", 1);
 
 Well, the int/uint error goes away (since function matching operates 
 differently than IFTI matching), but we've now got our old friend back 
 again -- the constant char[], wchar[], dchar[] mismatch problem.
I think a sound solution to this should be found. It's kind of hard, because char[] is the worst match but also probably the most used one. The most generous match is dchar[] but wastes much time and space for the minority of cases in which it's useful.
 How about another type of template? Here's one that does some simply 
 text processing:
 
 T[] layout(T) (T[] output, T[] format, T[][] subs...)
 
 This has an output buffer, a format string, and a set of optional args; 
 all of the same type. If I call it like so:
 
 char[128] tmp;
 char[]    world = "world";
 layout (tmp, "hello %1", world);
 
 that compiles ok. If I use wchar[] instead, it doesn't compile:
 
 wchar[128] tmp;
 wchar[]    world = "world";
 layout (tmp, "hello %1", world);
 
 In this case, the constant string used for formatting remains as a 
 char[], so the template match fails (args: wchar[], char[], wchar[])
 
 However, if I change the template signature to this instead:
 
 T[] layout(T) (T[] output, T[][] subs...)
 
 then everything works the way I want it to, but the design is actually 
 wrong (the format string is now not required). String constants can be a 
 royal PITA.
Color me convinced. :o) I have no bright ideas on solving it though.
 inout
 -----
 
 Since you're working on inout also, I'd like to ask what the plan is 
 relating to a couple of examples. Tango uses this style of call quite 
 regularly:
 
 get (inout int x);
 get (inout char[] x);
 etc.
 
 This is a clean way to pass-by-reference, instead of dealing with all 
 the pointer syntax. I sure hope D will retain reference semantics like 
 this, in some form?
It will, and in the same form.
 One current problem with inout, which you might not be aware of, is with 
 regard to const structs. I need to pass structs by reference, because I 
 don't want to pass them by value. Applying inout is the mechanism for 
 describing this:
 
 struct Bar {int a, b;}
 
 Bar b = {1, 2};
 
 void parf (inout Bar x) {}
 
 void main()
 {
    parf (b);
 }
 
 That all works fine. However, when I want to make those structs /const/ 
 instead, I cannot use inout since it has mutating semantics: I get a 
 compile error to that effect:
 
 const Bar b = {1, 2};
 
  >> Error: cannot modify const variable 'b'
 
 That is, there's no supported way to pass a const struct by reference. 
 The response from Walter in the past has been "just use a pointer 
 instead" ... well, yes I could do that. But it appears to be indicative 
 of a problem with the language design?
This case is on the list. You will definitely have a sane and simple way to pass const structs by reference, while having a guarantee that they can't be changed by the callee.
 Why do I want to use const? Well, the data held therein is for reference 
 only, and (via some future D vendor) I want that reference data placed 
 into a ROM segment. I do a lot of work with MCUs, and this sort of thing 
 is a common requirement.
I agree. Andrei
Jan 26 2007
next sibling parent reply kris <foo bar.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 [about implicit conversion rules]
 
 extern (C) int printf (char*, ...);

 class Foo
 {
         void write (int x) {printf("int\n");}
         void write (uint x) {printf("uint\n");}
         void write (long x) {printf("long\n");}
         void write (char x) {printf("char\n");}
         void write (wchar x) {printf("wchar\n");}
         void write (double x) {printf("double\n");}

         void write (char[] x) {printf("char[]\n");}
         void write (wchar[] x) {printf("wchar[]\n");}
 }

 void main()
 {
         auto foo = new Foo;

         foo.write ('c');
         foo.write (1);
         foo.write (1u);
         foo.write (3.14);
         //foo.write ("asa");
 }

 prints:

 char
 int
 uint
 double


 DMD has actually become smarter than the last time I tried something 
 like this: it manages to select the correct overload for 'c' whereas 
 before it couldn't decide whether int or uint was a better match for 
 the char instead. This is good.
 It seems clear from the above that D /defaults/ the type of character, 
 and undecorated integers, to something appropriate? In the above case 
 'c' is defaulted to char, rather than wchar, for example. The 
 undecorated int constant is defaulted to int, rather than uint or 
 long. This is good.
Yup. So far so good.
 Now for the broken part. When you uncomment the string constant, the 
 compiler gets all confused about whether it's a char[] or wchar[]. 
 There is no defaulting to one type, as there is for other constants 
 (such as char). It /is/ possible to decorate the string constant in a 
 similar manner to decorating integer constants:

 foo.write ("qwe"c);

 And this, of course, compiles. It's a PITA though, and differs from 
 the rules for other constants.
I talked to Walter about this and it's not a bug, it's a feature :o). Basically it's hard to decide what to do with an unadorned string when both wchar[] and char[] would want to "attract" it. I understand you're leaning towards defaulting to char[]? Then probably others will be unhappy.
You'll have noticed that the constant 'c' defaults to /char/, and that there's no compile-time conflict between the write(char) & write(wchar)? Are people unhappy about that too? Perhaps defaulting of char constants and int constants should be abolished also? I just want the compiler to be consistent, and it's quite unlikely that I'm alone in that regard -- consistency is a very powerful tool. Besides, aren't so-called 'features' actually bugs, coyly renamed by the marketing department? :) BTW: if you remove the write(char) overload, the compiler says it doesn't know which of int/uint overloads to select for 'c', and completely ignores write(wchar) as an viable option. That seems reasonable, but it clearly shows that a char constant is being defaulted to type char; and it's vaguely amusing in a twisted manner <g>
 Things start to go south when using templates with string constants. 
 For example, take this template sig:

 uint locate(T) (T[] source, T match, uint start=0)

 This is intended to handle types of char[], wchar[] and dchar[]. 
 There's a uint on the end, as opposed to an int. Suppose I call it 
 like this:

 locate ("abc", "ab", 1);

 we get a compile error, since the int-constant does not match a uint 
 in the sig (IFTI currently needs exact sig matches). In order to get 
 around this, we wrap the template with a few functions:

 uint locate (char[] source, char[] match, uint start=0)
 {
     return locateT!(char) (source, match, start);
 }

 uint locate (wchar[] source, wchar[] match, uint start=0)
 {
     return locateT!(wchar) (source, match, start);
 }

 and dchar too.

 Now we call it:

 locate ("abc", "ab", 1);

 Well, the int/uint error goes away (since function matching operates 
 differently than IFTI matching), but we've now got our old friend back 
 again -- the constant char[], wchar[], dchar[] mismatch problem.
I think a sound solution to this should be found. It's kind of hard, because char[] is the worst match but also probably the most used one. The most generous match is dchar[] but wastes much time and space for the minority of cases in which it's useful.
In the FWIW department, after writing several truckloads of text-oriented library code and wrappers for external text-processing libs, I've reached a simple conclusion: utf8 is where it's at for ~80% of code written, IMO. There's probably a ~15% need to go to utf32 for serious text handling (Word Processor, etc), and utf16 is the half-way house that the remaining percentage resort to when compromising (such as when stuffing things into ROM). Before the flames rise up and engulf that claim, Let's consider one major exclusion: certain GUI APIs use utf16 throughout. What the heck does one do in that situation if the compiler defaults strings-constants to char[] instead of wchar[]? Well, it's actually no issue at all since those APIs typically don't have method overloads for char/dchar also. They have only utf16 signatures instead, for any given method name, because they only deal in utf16. Thus, the compiler can happily morph a string constant to a wchar[] instead of the default -- *just as it happily does today* Let's also keep in mind we're talking string constants only, rather than all strings. I'll just try not to harp on about the consistency mismatch between char & char[] constants any more than I have done already.
 
 How about another type of template? Here's one that does some simply 
 text processing:

 T[] layout(T) (T[] output, T[] format, T[][] subs...)

 This has an output buffer, a format string, and a set of optional 
 args; all of the same type. If I call it like so:

 char[128] tmp;
 char[]    world = "world";
 layout (tmp, "hello %1", world);

 that compiles ok. If I use wchar[] instead, it doesn't compile:

 wchar[128] tmp;
 wchar[]    world = "world";
 layout (tmp, "hello %1", world);

 In this case, the constant string used for formatting remains as a 
 char[], so the template match fails (args: wchar[], char[], wchar[])

 However, if I change the template signature to this instead:

 T[] layout(T) (T[] output, T[][] subs...)

 then everything works the way I want it to, but the design is actually 
 wrong (the format string is now not required). String constants can be 
 a royal PITA.
Color me convinced. :o) I have no bright ideas on solving it though.
Perhaps a change to a default type might take care of it? After all, this particular issue is specific to string-constants only; not for other types (such as char/int/long/float). Certainly worth a try, one would think?
 
 inout
 -----

 Since you're working on inout also, I'd like to ask what the plan is 
 relating to a couple of examples. Tango uses this style of call quite 
 regularly:

 get (inout int x);
 get (inout char[] x);
 etc.

 This is a clean way to pass-by-reference, instead of dealing with all 
 the pointer syntax. I sure hope D will retain reference semantics like 
 this, in some form?
It will, and in the same form.
Grand!
 
 One current problem with inout, which you might not be aware of, is 
 with regard to const structs. I need to pass structs by reference, 
 because I don't want to pass them by value. Applying inout is the 
 mechanism for describing this:

 struct Bar {int a, b;}

 Bar b = {1, 2};

 void parf (inout Bar x) {}

 void main()
 {
    parf (b);
 }

 That all works fine. However, when I want to make those structs 
 /const/ instead, I cannot use inout since it has mutating semantics: I 
 get a compile error to that effect:

 const Bar b = {1, 2};

  >> Error: cannot modify const variable 'b'

 That is, there's no supported way to pass a const struct by reference. 
 The response from Walter in the past has been "just use a pointer 
 instead" ... well, yes I could do that. But it appears to be 
 indicative of a problem with the language design?
This case is on the list. You will definitely have a sane and simple way to pass const structs by reference, while having a guarantee that they can't be changed by the callee.
Praise the lord !
 Why do I want to use const? Well, the data held therein is for 
 reference only, and (via some future D vendor) I want that reference 
 data placed into a ROM segment. I do a lot of work with MCUs, and this 
 sort of thing is a common requirement.
I agree. Andrei
Cheers; this kind of detailed reply (above) is very much appreciated - Kris
Jan 26 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 [about implicit conversion rules]

 extern (C) int printf (char*, ...);

 class Foo
 {
         void write (int x) {printf("int\n");}
         void write (uint x) {printf("uint\n");}
         void write (long x) {printf("long\n");}
         void write (char x) {printf("char\n");}
         void write (wchar x) {printf("wchar\n");}
         void write (double x) {printf("double\n");}

         void write (char[] x) {printf("char[]\n");}
         void write (wchar[] x) {printf("wchar[]\n");}
 }

 void main()
 {
         auto foo = new Foo;

         foo.write ('c');
         foo.write (1);
         foo.write (1u);
         foo.write (3.14);
         //foo.write ("asa");
 }

 prints:

 char
 int
 uint
 double
[snip]
 Now for the broken part. When you uncomment the string constant, the 
 compiler gets all confused about whether it's a char[] or wchar[]. 
 There is no defaulting to one type, as there is for other constants 
 (such as char). It /is/ possible to decorate the string constant in a 
 similar manner to decorating integer constants:

 foo.write ("qwe"c);

 And this, of course, compiles. It's a PITA though, and differs from 
 the rules for other constants.
I talked to Walter about this and it's not a bug, it's a feature :o). Basically it's hard to decide what to do with an unadorned string when both wchar[] and char[] would want to "attract" it. I understand you're leaning towards defaulting to char[]? Then probably others will be unhappy.
You'll have noticed that the constant 'c' defaults to /char/, and that there's no compile-time conflict between the write(char) & write(wchar)? Are people unhappy about that too? Perhaps defaulting of char constants and int constants should be abolished also?
It's a bit more complicated with character literals than just defaulting to 'char': ----- import std.stdio; void main() { writefln(typeid(typeof('a'))); // c <= \u007f writefln(typeid(typeof('\uabcd'))); // c <= \uffff writefln(typeid(typeof('\U000abcde'))); // c <= \U0010ffff } ----- outputs: """ char wchar dchar """ So it defaults to the *smallest* character type that can hold it in one element. Pretty cool, actually. This also applies to other types, by the way. If you type an integer literal that won't fit into an 'int', it'll be a 'long' constant (assuming it fits), not an 'int' constant. Perhaps we should do something similar with string literals, defaulting it to use an array of the smallest character type that can hold all of the characters in the string (i.e. the maximum "character type" of the component characters) ? That seems like a reasonable rule. And it has the added benefit that "some string constant".length will always be the number of characters in the string.
Jan 27 2007
next sibling parent kris <foo bar.com> writes:
Frits van Bommel wrote:
 kris wrote:
 
 Andrei Alexandrescu (See Website For Email) wrote:

 kris wrote:
 [about implicit conversion rules]

 extern (C) int printf (char*, ...);

 class Foo
 {
         void write (int x) {printf("int\n");}
         void write (uint x) {printf("uint\n");}
         void write (long x) {printf("long\n");}
         void write (char x) {printf("char\n");}
         void write (wchar x) {printf("wchar\n");}
         void write (double x) {printf("double\n");}

         void write (char[] x) {printf("char[]\n");}
         void write (wchar[] x) {printf("wchar[]\n");}
 }

 void main()
 {
         auto foo = new Foo;

         foo.write ('c');
         foo.write (1);
         foo.write (1u);
         foo.write (3.14);
         //foo.write ("asa");
 }

 prints:

 char
 int
 uint
 double
[snip]
 Now for the broken part. When you uncomment the string constant, the 
 compiler gets all confused about whether it's a char[] or wchar[]. 
 There is no defaulting to one type, as there is for other constants 
 (such as char). It /is/ possible to decorate the string constant in 
 a similar manner to decorating integer constants:

 foo.write ("qwe"c);

 And this, of course, compiles. It's a PITA though, and differs from 
 the rules for other constants.
I talked to Walter about this and it's not a bug, it's a feature :o). Basically it's hard to decide what to do with an unadorned string when both wchar[] and char[] would want to "attract" it. I understand you're leaning towards defaulting to char[]? Then probably others will be unhappy.
You'll have noticed that the constant 'c' defaults to /char/, and that there's no compile-time conflict between the write(char) & write(wchar)? Are people unhappy about that too? Perhaps defaulting of char constants and int constants should be abolished also?
It's a bit more complicated with character literals than just defaulting to 'char': ----- import std.stdio; void main() { writefln(typeid(typeof('a'))); // c <= \u007f writefln(typeid(typeof('\uabcd'))); // c <= \uffff writefln(typeid(typeof('\U000abcde'))); // c <= \U0010ffff } ----- outputs: """ char wchar dchar """ So it defaults to the *smallest* character type that can hold it in one element. Pretty cool, actually. This also applies to other types, by the way. If you type an integer literal that won't fit into an 'int', it'll be a 'long' constant (assuming it fits), not an 'int' constant.
Yes. Kinda glossed over that part, didn't I <g>
 Perhaps we should do something similar with string literals, defaulting 
 it to use an array of the smallest character type that can hold all of 
 the characters in the string (i.e. the maximum "character type" of the 
 component characters) ?
 That seems like a reasonable rule. And it has the added benefit that 
 "some string constant".length will always be the number of characters in 
 the string.
Yes, that would seem eminently reasonable.
Jan 27 2007
prev sibling next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Frits van Bommel wrote:

 So it defaults to the *smallest* character type that can hold it in one 
 element.
 Pretty cool, actually.
 
 This also applies to other types, by the way. If you type an integer 
 literal that won't fit into an 'int', it'll be a 'long' constant 
 (assuming it fits), not an 'int' constant.
 
 Perhaps we should do something similar with string literals, defaulting 
 it to use an array of the smallest character type that can hold all of 
 the characters in the string (i.e. the maximum "character type" of the 
 component characters) ?
And while you're at it do the same for integer and float literals -- use the "best" type considering _all_ the elements rather than just the first one so that this doesn't mean an array with two 1 integers. [1, 1.5]; --bb
Jan 27 2007
prev sibling parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Frits van Bommel wrote:
 kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 [about implicit conversion rules]
         void write (char[] x) {printf("char[]\n");}
         void write (wchar[] x) {printf("wchar[]\n");}
         //foo.write ("asa");
 And this, of course, compiles. It's a PITA though, and differs from 
 the rules for other constants.
I talked to Walter about this and it's not a bug, it's a feature :o). Basically it's hard to decide what to do with an unadorned string when both wchar[] and char[] would want to "attract" it. I understand you're leaning towards defaulting to char[]? Then probably others will be unhappy.
A slight inconsistency here is that: foo("test"); gives a syntax error, while: auto t = "test"; foo(t); compiles and selects the char[] overload. Also: void bar(T)(T x) { foo(x); } ... bar("test"); compiles and selects the char[] overload.
 It's a bit more complicated with character literals than just defaulting 
 to 'char':
[snip typeof('e') == char, typeof('é') == wchar, ...]]
 So it defaults to the *smallest* character type that can hold it in one 
 element.
 Pretty cool, actually.
A problem related to this, that might actually warrant a bug report, is that given: char[] str = "abcd"; str ~= 'e'; writefln(str); Is OK, while str ~= 'é'; writefln(str); Generates an invalid UTF-8 string ant gives the runtime "Error: 4invalid UTF-8 sequence" It seems like the wchar 'é' gets int-promoted to 0xE9 that is different from the char[] (utf-8) representation of "é", that would be x"C3 A9". -- /Oskar
Jan 29 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Oskar Linde wrote:
 Frits van Bommel wrote:
 It's a bit more complicated with character literals than just 
 defaulting to 'char':
[snip typeof('e') == char, typeof('é') == wchar, ...]]
 So it defaults to the *smallest* character type that can hold it in 
 one element.
 Pretty cool, actually.
A problem related to this, that might actually warrant a bug report, is that given: char[] str = "abcd"; str ~= 'e'; writefln(str); Is OK, while str ~= 'é'; writefln(str); Generates an invalid UTF-8 string ant gives the runtime "Error: 4invalid UTF-8 sequence" It seems like the wchar 'é' gets int-promoted to 0xE9 that is different from the char[] (utf-8) representation of "é", that would be x"C3 A9".
Yes, that probably warrants a bug report. Either an error should be produced (something like "Invalid operation: str ~= 'é', cannot concatenate wchar to char[]"), or (preferably) it should just work. The latter can (for example) be implemented by doing the equivalent of std.utf.encode(str, 'é'), or the equivalent of str ~= "é" (note the quotes, implicitly converting it to char[] instead of wchar).
Jan 29 2007
prev sibling parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 Now we call it:

 locate ("abc", "ab", 1);

 Well, the int/uint error goes away (since function matching operates 
 differently than IFTI matching), but we've now got our old friend back 
 again -- the constant char[], wchar[], dchar[] mismatch problem.
I think a sound solution to this should be found. It's kind of hard, because char[] is the worst match but also probably the most used one. The most generous match is dchar[] but wastes much time and space for the minority of cases in which it's useful.
I suppose this is where the difference of opinion comes in, but why do you think char[] is the worst match? From what I understand, UTF-8 seems preferable to UTF-16 in most cases. The algorithm for dealing with such strings must be essentially the same in both cases, and UTF-8 tends to be more compact on average. Sean
Jan 27 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 kris wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 [snip]
...
 3. I want to increase community's awareness of the importance of 
 those tests for larger use cases, as well as gather ideas from the 
 community about how to overcome D's shortcomings in passing the tests.
...
 That being said, my view on what should be fixed in D is a bit 
 different. For example, as I said, one of my main foci is putting a lead 
 sarcophagus around "inout" before its radioactive waste affects too much 
 code that will become broken later. Also, implicit conversion rules will 
 change, and again I want to get that in ASAP so not too much code 
 suffers. And so on.
...
 Andrei
Well, since you mention it... I'm not positive that I'm thinking of the same type problem that you are, but when writing templates in D, I've wanted something like storageof(T) capabilities, but the way I imagine it, the ideal solution would be a bit different. (Actually, I'm not sure exactly what problem inout causes that you are referring to as requiring lead, but moving on..) I would prefer for the types passed to a template to carry a lot more info across, and let the user decide which parts to throw away. You could call this a "strict" template, if you want backward compatibility (but it's early in D's template design so maybe we don't care). I'm thinking that this (current) syntax: template foo(T) { T plus(T x, T y) { return x + y; } } would be equivalent to: strict template foo(T) { T.value plus(T.value x, T.value y) { return x + y; } } In a 'strict' template the type "T" would carry a lot of baggage. It would know if T was constant (as in a local "const int x = 10"), or a constant view (maybe) whether it is an lvalue, rvalue, literal, known-at-compile-time, static array, result of arithmetic, etc. I couldn't tell you the actual names and definitions of all of these attributes -- I imagine they could be added one at a time as the need arose, starting with an equivalent for "storageof". [OT: I personally don't like the name storageof() because 'storage class' doesn't connote 'inout' versus 'in' to me, I don't really think of those as storage. I guess it comes from the idea of storing const items in R/O memory?) What we normally think of as the "type" is called T.value in a strict template. If you use "T" you get a whole lot more -- in fact, we might want to force the user to say "T.all" in this case. In my view, what we have now, is a leaky abstraction - you get some type info but not a lot. When you pass T into a template, you lose bits and pieces of what makes T work. There would be two ways to manage the template definition - one is to get out each piece of 'type modification' data that you want to copy to the new type, using something like: If T is "const long" (but not lazy), then: int foo(T.constness T.lazyness T.value x) (becomes: int foo(const long x)) Alternately, you could let users specify things like "in T" and the "in" overrides the "inout" if there was one. Kevin
Jan 26 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 I would prefer for the types passed to a template to carry a lot more 
 info across, and let the user decide which parts to throw away.  You 
 could call this a "strict" template, if you want backward compatibility 
 (but it's early in D's template design so maybe we don't care).
 
 I'm thinking that this (current) syntax:
 
 template foo(T) {
    T plus(T x, T y) { return x + y; }
 }
 
 would be equivalent to:
 
 strict template foo(T) {
     T.value plus(T.value x, T.value y) { return x + y; }
 }
 
 In a 'strict' template the type "T" would carry a lot of baggage.  It 
 would know if T was constant (as in a local "const int x = 10"), or a 
 constant view (maybe) whether it is an lvalue, rvalue, literal, 
 known-at-compile-time, static array, result of arithmetic, etc.
 
 I couldn't tell you the actual names and definitions of all of these 
 attributes -- I imagine they could be added one at a time as the need 
 arose, starting with an equivalent for "storageof".  
[snip] These are great points. The direction I hope to steer things in would be to (backward-compatibly) continue matching types "lossily", but to also give you the ability to pattern-match the extra info when you want. The syntax that I am proposing does away with storageof and is very much in spirit with the current D: S int foo(S, T)(S T t) { } It does exactly what it says it does, in a clear and terse manner, and is 100% within the spirit of existing D: * It's customized on symbols S and T * It's matching (by sheer position of S and T) the storage (i.e., all of the gooey fuzzy nice information about the argument passed) and the type of the incoming argument * In this example it uses the storage in the result type (e.g. const goes to const) * Inside the function can easily use either T separately or S T as a group that transports the storage around. You have total flexibility (and don't forget that juxtaposition is always easier than extraction). So far we're only thinking of storage-like attributes, but a good deal of information can be encoded under the loose declaration of "storage". Andrei
Jan 26 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 The syntax that I am proposing does away with storageof and is very much 
 in spirit with the current D:
 
 S int foo(S, T)(S T t) { }
 
 It does exactly what it says it does, in a clear and terse manner, and 
 is 100% within the spirit of existing D:
 
 * It's customized on symbols S and T
 
 * It's matching (by sheer position of S and T) the storage (i.e., all of 
 the gooey fuzzy nice information about the argument passed) and the type 
 of the incoming argument
 
 * In this example it uses the storage in the result type (e.g. const 
 goes to const)
 
 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).
 
 So far we're only thinking of storage-like attributes, but a good deal 
 of information can be encoded under the loose declaration of "storage".
Would it also be possible to 'cherry-pick' attributes? So that e.g something like S.constness expands to either 'const' or ''? And would this mean 'raw' storage attributes would be valid template parameters? So that something like foo!(const) would be valid syntax?
Jan 26 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Frits van Bommel wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 The syntax that I am proposing does away with storageof and is very 
 much in spirit with the current D:

 S int foo(S, T)(S T t) { }

 It does exactly what it says it does, in a clear and terse manner, and 
 is 100% within the spirit of existing D:

 * It's customized on symbols S and T

 * It's matching (by sheer position of S and T) the storage (i.e., all 
 of the gooey fuzzy nice information about the argument passed) and the 
 type of the incoming argument

 * In this example it uses the storage in the result type (e.g. const 
 goes to const)

 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).

 So far we're only thinking of storage-like attributes, but a good deal 
 of information can be encoded under the loose declaration of "storage".
Would it also be possible to 'cherry-pick' attributes? So that e.g something like S.constness expands to either 'const' or ''? And would this mean 'raw' storage attributes would be valid template parameters? So that something like foo!(const) would be valid syntax?
This gets into something that would be nice to have in D: some sort of concept checking. The easiest method would be something like this: alias Tuple!(int,char) A; void fnA( T in A )( T val ) {} void fnB( T : is( T == int ) || is( T == char ) )( T val ) {} The ':' currently suggests type matching so perhaps the operator would need to be changes. An alternate for the above which would work is: template fnC( T ) in { this = is( T == int ) || is( T == char ); } body { } Which would be equivalent to: template fnC( T, bool match : true = Test!(T) ) { } template Test( T ) { const Test = is( T == int ) || is( T == char ); } The problem is that it's awkward to overload templates on concepts now--the C++ approach is just about the only way. However, since templates can not be overloaded across module boundaries, this is also currently an option: template fnD( T ) { static if( is( T == int ) || is( T == char ) ) { ... } else { static assert( false ); } } It works just fine, but requires all overloads to exist within the same template block, and this isn't terribly readable with a large number of conditions. Sean
Jan 26 2007
prev sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Frits van Bommel wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 The syntax that I am proposing does away with storageof and is very 
 much in spirit with the current D:

 S int foo(S, T)(S T t) { }

 It does exactly what it says it does, in a clear and terse manner, and 
 is 100% within the spirit of existing D:

 * It's customized on symbols S and T

 * It's matching (by sheer position of S and T) the storage (i.e., all 
 of the gooey fuzzy nice information about the argument passed) and the 
 type of the incoming argument

 * In this example it uses the storage in the result type (e.g. const 
 goes to const)

 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).

 So far we're only thinking of storage-like attributes, but a good deal 
 of information can be encoded under the loose declaration of "storage".
Would it also be possible to 'cherry-pick' attributes? So that e.g something like S.constness expands to either 'const' or ''? And would this mean 'raw' storage attributes would be valid template parameters? So that something like foo!(const) would be valid syntax?
You will be able to cherry-pick with "is" tests. Walter is opposed to manipulating the raw storage attributes. Andrei
Jan 26 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Frits van Bommel wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 The syntax that I am proposing does away with storageof and is very 
 much in spirit with the current D:

 S int foo(S, T)(S T t) { }

 It does exactly what it says it does, in a clear and terse manner, 
 and is 100% within the spirit of existing D:

 * It's customized on symbols S and T

 * It's matching (by sheer position of S and T) the storage (i.e., all 
 of the gooey fuzzy nice information about the argument passed) and 
 the type of the incoming argument

 * In this example it uses the storage in the result type (e.g. const 
 goes to const)

 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).

 So far we're only thinking of storage-like attributes, but a good 
 deal of information can be encoded under the loose declaration of 
 "storage".
Would it also be possible to 'cherry-pick' attributes? So that e.g something like S.constness expands to either 'const' or ''? And would this mean 'raw' storage attributes would be valid template parameters? So that something like foo!(const) would be valid syntax?
You will be able to cherry-pick with "is" tests. Walter is opposed to manipulating the raw storage attributes.
If you have to cherry pick with "is" tests to separate the bits out of the S above, then why not just use "is" tests to pick the S info off the type to begin with? --bb
Jan 26 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Bill Baxter wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 You will be able to cherry-pick with "is" tests. Walter is opposed to 
 manipulating the raw storage attributes.
If you have to cherry pick with "is" tests to separate the bits out of the S above, then why not just use "is" tests to pick the S info off the type to begin with?
Because just 'S' is much shorter when you don't want to cherry-pick?
Jan 27 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Frits van Bommel wrote:
 Bill Baxter wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 You will be able to cherry-pick with "is" tests. Walter is opposed to 
 manipulating the raw storage attributes.
If you have to cherry pick with "is" tests to separate the bits out of the S above, then why not just use "is" tests to pick the S info off the type to begin with?
Because just 'S' is much shorter when you don't want to cherry-pick?
Just wondering if there are that many cases where all want to do is pick off S without 'deconstructing' it further. Especially given Andrei's statement "there are lots of things that could go under the vague heading of storage class", it seems like it may be rare that you can actually just use S as-is without picking out the particular attributes you care about -- const or inout or any one of the 'lots of other things'. And if that's the case then maybe the benefits of having the "template foo(S T)(S T x)" syntax are not so huge. You could get S using alias storageof(T) S in the first line of the template instead. Or lets say you just want to use the constness part of S, which seems like a fairly common thing to want. Then you'll still need something like: constof(S) template foo(S T)(S T v) { ... } But if you have to say constof(S) you might as well just make constof operate on T directly instead. constof(T) template foo(T)(T v) { ... } I'm willing to be convinced, but so far it's not clear to me that "template xx(S T)" would be all that useful in practice. --bb
Jan 27 2007
prev sibling next sibling parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Kevin Bealer wrote:
 I would prefer for the types passed to a template to carry a lot more 
 info across, and let the user decide which parts to throw away.  You 
 could call this a "strict" template, if you want backward 
 compatibility (but it's early in D's template design so maybe we don't 
 care).

 I'm thinking that this (current) syntax:

 template foo(T) {
    T plus(T x, T y) { return x + y; }
 }

 would be equivalent to:

 strict template foo(T) {
     T.value plus(T.value x, T.value y) { return x + y; }
 }

 In a 'strict' template the type "T" would carry a lot of baggage.  It 
 would know if T was constant (as in a local "const int x = 10"), or a 
 constant view (maybe) whether it is an lvalue, rvalue, literal, 
 known-at-compile-time, static array, result of arithmetic, etc.

 I couldn't tell you the actual names and definitions of all of these 
 attributes -- I imagine they could be added one at a time as the need 
 arose, starting with an equivalent for "storageof".  
[snip] These are great points. The direction I hope to steer things in would be to (backward-compatibly) continue matching types "lossily", but to also give you the ability to pattern-match the extra info when you want. The syntax that I am proposing does away with storageof and is very much in spirit with the current D: S int foo(S, T)(S T t) { }
Interesting. So I suppose I could explicitly instantiate the template as: int i; foo!(const)( i ); If so, this seems great.
 So far we're only thinking of storage-like attributes, but a good deal 
 of information can be encoded under the loose declaration of "storage".
Definitely. Sean
Jan 26 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Kevin Bealer wrote:
 I would prefer for the types passed to a template to carry a lot more 
 info across, and let the user decide which parts to throw away.  You 
 could call this a "strict" template, if you want backward 
 compatibility (but it's early in D's template design so maybe we don't 
 care).

 I'm thinking that this (current) syntax:

 template foo(T) {
    T plus(T x, T y) { return x + y; }
 }

 would be equivalent to:

 strict template foo(T) {
     T.value plus(T.value x, T.value y) { return x + y; }
 }

 In a 'strict' template the type "T" would carry a lot of baggage.  It 
 would know if T was constant (as in a local "const int x = 10"), or a 
 constant view (maybe) whether it is an lvalue, rvalue, literal, 
 known-at-compile-time, static array, result of arithmetic, etc.

 I couldn't tell you the actual names and definitions of all of these 
 attributes -- I imagine they could be added one at a time as the need 
 arose, starting with an equivalent for "storageof".  
[snip] These are great points. The direction I hope to steer things in would be to (backward-compatibly) continue matching types "lossily", but to also give you the ability to pattern-match the extra info when you want. The syntax that I am proposing does away with storageof and is very much in spirit with the current D: S int foo(S, T)(S T t) { } It does exactly what it says it does, in a clear and terse manner, and is 100% within the spirit of existing D: * It's customized on symbols S and T * It's matching (by sheer position of S and T) the storage (i.e., all of the gooey fuzzy nice information about the argument passed) and the type of the incoming argument * In this example it uses the storage in the result type (e.g. const goes to const) * Inside the function can easily use either T separately or S T as a group that transports the storage around. You have total flexibility (and don't forget that juxtaposition is always easier than extraction). So far we're only thinking of storage-like attributes, but a good deal of information can be encoded under the loose declaration of "storage". Andrei
I like this; does this mean that when declaring an instance, you would do something like this? Or do the 'const' parts get deduced? alias Foo!(const, int) TheFoo; Kevin
Jan 26 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
[about deducing storage types]
 The syntax that I am proposing does away with storageof and is very 
 much in spirit with the current D:

 S int foo(S, T)(S T t) { }

 It does exactly what it says it does, in a clear and terse manner, and 
 is 100% within the spirit of existing D:

 * It's customized on symbols S and T

 * It's matching (by sheer position of S and T) the storage (i.e., all 
 of the gooey fuzzy nice information about the argument passed) and the 
 type of the incoming argument

 * In this example it uses the storage in the result type (e.g. const 
 goes to const)

 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).

 So far we're only thinking of storage-like attributes, but a good deal 
 of information can be encoded under the loose declaration of "storage".


 Andrei
I like this; does this mean that when declaring an instance, you would do something like this? Or do the 'const' parts get deduced? alias Foo!(const, int) TheFoo;
The intent is to deduce the storage, but specifying it explicitly works just as well. There is strong resistance against my notation because manipulating the storage class separately is something entirely new, which means a lot of work in the compiler implementation. Also, S is not a type but a (new kind of) alias, which might confuse people who read: template Foo(S, T) { ... } and expect S and T to be types, just to discover in the body of the template that S is actually a qualifier. The strawman we're discussing now looks like this: void foo(auto T)(T t) { } meaning, T will match whatever you throw at foo, including storage: int a = 5; foo(a); // T == inout int foo(a + 1); // T == int Either way, one thing is clear - defining storage classes properly is a focal area. Andrei
Jan 26 2007
parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Kevin Bealer wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
[about deducing storage types]
 The syntax that I am proposing does away with storageof and is very 
 much in spirit with the current D:

 S int foo(S, T)(S T t) { }

 It does exactly what it says it does, in a clear and terse manner, 
 and is 100% within the spirit of existing D:

 * It's customized on symbols S and T

 * It's matching (by sheer position of S and T) the storage (i.e., all 
 of the gooey fuzzy nice information about the argument passed) and 
 the type of the incoming argument

 * In this example it uses the storage in the result type (e.g. const 
 goes to const)

 * Inside the function can easily use either T separately or S T as a 
 group that transports the storage around. You have total flexibility 
 (and don't forget that juxtaposition is always easier than extraction).

 So far we're only thinking of storage-like attributes, but a good 
 deal of information can be encoded under the loose declaration of 
 "storage".


 Andrei
I like this; does this mean that when declaring an instance, you would do something like this? Or do the 'const' parts get deduced? alias Foo!(const, int) TheFoo;
The intent is to deduce the storage, but specifying it explicitly works just as well. There is strong resistance against my notation because manipulating the storage class separately is something entirely new, which means a lot of work in the compiler implementation. Also, S is not a type but a (new kind of) alias, which might confuse people who read: template Foo(S, T) { ... } and expect S and T to be types, just to discover in the body of the template that S is actually a qualifier. The strawman we're discussing now looks like this: void foo(auto T)(T t) { } meaning, T will match whatever you throw at foo, including storage: int a = 5; foo(a); // T == inout int foo(a + 1); // T == int
So how would one declare such a type? typeof(T) perhaps? I imagine there must be some way to separate the storage class from the type for this to work. Sean
Jan 27 2007
prev sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"kris" <foo bar.com> wrote in message 
news:ep6c92$2ti3$1 digitaldaemon.com...
 Like, for instance, an updated GC? When did that ever top the list of 
 real-world issues for D?
I'm often running into problems with the (old!) GC, and the only fix is to manage memory manually here-n-there, until you end up with some mixed GC, malloc/free frankenstein code. I surely hope the new GC will solve my problems :) L.
Jan 23 2007
parent reply kris <foo bar.com> writes:
Lionello Lunesu wrote:
 "kris" <foo bar.com> wrote in message 
 news:ep6c92$2ti3$1 digitaldaemon.com...
 
Like, for instance, an updated GC? When did that ever top the list of 
real-world issues for D?
I'm often running into problems with the (old!) GC, and the only fix is to manage memory manually here-n-there, until you end up with some mixed GC, malloc/free frankenstein code. I surely hope the new GC will solve my problems :)
Yeah, I agree. There's things that each of us would like resolved. For example, phobos deadlocks in the gc when using multiple threads - sometimes only beyond a day or two - and it's been that way since before last April. But, that affects only a select few people. In fact, it seems I'm the only one who managed to trip it (though Sean fixed it in both Tango & Ares). From another viewpoint, lib issues and debugger issues tend to affect the public in general; especially those new to the language, who may be used to the relative luxury of functional debuggers and libs :-D On one hand, I was bitchin' that priorities seem as crazy as the recent weather up north. On the other, I was relaying a subtle but serious concern about the direction of D, and its general daily usability. While I often bitch and gripe about the language and its various flaws, and it's clear that Walter and I can't agree on anything notable these days, I'll happily stick by the assertion that D has awesome potential (and will keep putting serious effort into improving the few areas that I /think/ I can contribute to). All the same, I suspect that now it's more a case of the "managment" of how D progresses which will be the deciding factor whether it lives or dies by the corporate hand (phew! There's a run-on). That's just an opinion, but it's what drove the response to Andrei's post, along with my mistaken belief he was tied in somewhat more deeply to the progression of D. My apologies for placing /that/ selection of real-world usability issues above the troubles you've been having with the GC ;) It was silly of me to even make mention of it. - Kris
Jan 23 2007
parent reply Sean Kelly <sean f4.ca> writes:
kris wrote:
 Lionello Lunesu wrote:
 "kris" <foo bar.com> wrote in message 
 news:ep6c92$2ti3$1 digitaldaemon.com...

 Like, for instance, an updated GC? When did that ever top the list of 
 real-world issues for D?
I'm often running into problems with the (old!) GC, and the only fix is to manage memory manually here-n-there, until you end up with some mixed GC, malloc/free frankenstein code. I surely hope the new GC will solve my problems :)
Could you explain further? Is this simply a problem with false positives or is there something else going on?
 Yeah, I agree. There's things that each of us would like resolved. For 
 example, phobos deadlocks in the gc when using multiple threads - 
 sometimes only beyond a day or two - and it's been that way since before 
  last April. But, that affects only a select few people. In fact, it 
 seems I'm the only one who managed to trip it (though Sean fixed it in 
 both Tango & Ares).
For the record, I read through the Phobos code looking for the problem but nothing jumped out at me (and the lack of a usable stack trace didn't help much). And the Tango/Ares Thread code is a complete rewrite so I couldn't simply submit my version to Walter for inclusion in Phobos.
  From another viewpoint, lib issues and debugger issues tend to affect 
 the public in general; especially those new to the language, who may be 
 used to the relative luxury of functional debuggers and libs :-D
I've gotten pretty used to debugging via code inspection and an occasional well-placed printf since discovering D, but the library issues are obviously a major point of concern with Tango in development. There are certainly far fewer of them then there were a year ago, but a few major stumbling blocks still exist. Sean
Jan 24 2007
next sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"Sean Kelly" <sean f4.ca> wrote in message 
news:ep8a08$2t4o$1 digitaldaemon.com...
 Could you explain further?  Is this simply a problem with false positives 
 or is there something else going on?
I'm afraid I'm not 100% sure. The memory use was definitely higher than it should be and there were quite a few memory leaks (false positives). The biggest problem however was not the memory use, but the fact that the GC would get slower and slower. This particular application was using mostly strings (big ones), so apart from the pointers to those strings, there werent' many pointers. L.
Jan 24 2007
parent Sean Kelly <sean f4.ca> writes:
Lionello Lunesu wrote:
 "Sean Kelly" <sean f4.ca> wrote in message 
 news:ep8a08$2t4o$1 digitaldaemon.com...
 Could you explain further?  Is this simply a problem with false positives 
 or is there something else going on?
I'm afraid I'm not 100% sure. The memory use was definitely higher than it should be and there were quite a few memory leaks (false positives). The biggest problem however was not the memory use, but the fact that the GC would get slower and slower. This particular application was using mostly strings (big ones), so apart from the pointers to those strings, there werent' many pointers.
Ah, that is because all those strings are being scanned for pointers. The Tango GC that keys on element size addresses this issue, and the new Phobos GC that uses TypeInfo does as well. For the record, Tango will be using TypeInfo just like Phobos as soon as the next DMD release is out (we're holding with 1.0 until the bugs are fixed). Sean
Jan 24 2007
prev sibling parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Sean Kelly schrieb am 2007-01-24:
[...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ? Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFt7+fLK5blCcjpWoRAgKcAJ48NLoOIYlBQJr3ZiCMsCGJ2sggGwCgkNEZ pLO502XddDa5Rcmo9ZCmCHc= =gXEq -----END PGP SIGNATURE-----
Jan 24 2007
next sibling parent reply Sean Kelly <sean f4.ca> writes:
Thomas Kuehne wrote:
 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1
 
 Sean Kelly schrieb am 2007-01-24:
 [...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ?
No. Would it work for a deadlock situation? Sean
Jan 24 2007
parent Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Sean Kelly schrieb am 2007-01-24:
 Thomas Kuehne wrote:
 Sean Kelly schrieb am 2007-01-24:
 [...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ?
No. Would it work for a deadlock situation?
I think it should. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFt+t1LK5blCcjpWoRAveqAJ9QPCTrhW0hinZvZc0YRUb7LS+FlACgrZrd g6XbkivwRylFQG09S5+HQWM= =MUkv -----END PGP SIGNATURE-----
Jan 24 2007
prev sibling next sibling parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Thomas Kuehne schrieb am 2007-01-24:
 Sean Kelly schrieb am 2007-01-24:
 [...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ?
The latest version contains a convenince function for gdb. Simply issue the gdb command "call print_trace ($ebp)" to print a stack trace. Sample session: [...] Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFt+kwLK5blCcjpWoRAuMEAJwJRj4bDV0iOT3AEPVc+iPECYLiMwCfeiFq qssaJ0ke1hFWvMCn6laWMco= =D9Ih -----END PGP SIGNATURE-----
Jan 24 2007
parent Sean Kelly <sean f4.ca> writes:
Thomas Kuehne wrote:
 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1
 
 Thomas Kuehne schrieb am 2007-01-24:
 Sean Kelly schrieb am 2007-01-24:
 [...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ?
The latest version contains a convenince function for gdb. Simply issue the gdb command "call print_trace ($ebp)" to print a stack trace. Sample session: [...]
Very nice! I may just start doing all my D debugging on Linux. Sean
Jan 24 2007
prev sibling parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Thomas Kuehne schrieb am 2007-01-24:
 Sean Kelly schrieb am 2007-01-24:
 [...]
 For the record, I read through the Phobos code looking for the problem 
 but nothing jumped out at me (and the lack of a usable stack trace 
 didn't help much). 
Have you tried Flectioned's Trace.getTrace() ?
The latest version contains a convenience function for gdb. Simply issue the gdb command "call print_trace ($ebp)" to print a stack trace. Sample session: [...] Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFt+odLK5blCcjpWoRApXOAKCm9jDp8HH0nCzUlLhaqQczcBYntACgrovA L0LcNxr4ciRizu9V21JPL+I= =/PIb -----END PGP SIGNATURE-----
Jan 24 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Thomas Kuehne wrote:
[snip]

Hey, another post from the future...
(Please check your computer's clock)
Jan 24 2007