www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Notes on D future

reply bearophile <bearophileHUGS lycos.com> writes:
I have just read the interesting document "WalterAndrei.pdf" recently linked
here. I don't understand all the things it says, but here are few notes:

Page 17-18: the support of pure functions look like an intelligent idea, but I
don't know their syntax, how they will be/look, etc.


Page 23-25, polysemous values: they will be useful for various things, not just
integers. But I don't think they help avoid other kinds of integer
overflow/undeflow (some of them are avoided by the Delphi compiler), like
adding two big integers that produces wrap-around, etc (eventually such extra
cheeks on integer operations can disabled by the -release compilation flag).


Page 33: I think some of those "optimizations based on constant folding" can be
done from just a single function (that the compiler automatically transforms
into a kind of "template", copying it as necessary), using some partial
compilation on a copy of the function itself. So a function like:
int foo(int x, int y) {...}
If called like:
a = foo(something(), y);
b = foo(3, y);
Can be automatically managed by the compiler as it was a pair of:
int foo(int x, int y) {...}
int foo(int x)(int y) {...}


Page 52: can't the compiler automatically unroll little loops based on constant
intervals/loop vars? (another compilation flag like -unroll can added, if
necessary). I presume "static foreach" is a way for the programmer to tell the
compiler that he/she really wants such unrolling done, so if such unrolling
isn't possible, then the compiler must raise a compilation error instead of
just silently leave the loop unrolled. At the moment I think tuples are looped
with a implicitly static foreach. Is such implicit "static" going to become
explicit and necessary? So this raises a compilation error:
void foo(Types...)(Types args) {
  foreach (arg; args)
    bar(arg);
...
And you must write:
void foo(Types...)(Types args) {
  static foreach (arg; args)
    bar(arg);
...

Bye,
bearophile
Aug 29 2007
parent reply Reiner Pope <some address.com> writes:
bearophile wrote:
 I have just read the interesting document "WalterAndrei.pdf" recently linked
here. I don't understand all the things it says, but here are few notes:
 
 Page 17-18: the support of pure functions look like an intelligent idea, but I
don't know their syntax, how they will be/look, etc.
 
Presumably just an annotation like "pure" on the function should be enough: pure int add(int x, int y) { return x + y; }
 
 Page 23-25, polysemous values: they will be useful for various things, not
just integers. But I don't think they help avoid other kinds of integer
overflow/undeflow (some of them are avoided by the Delphi compiler), like
adding two big integers that produces wrap-around, etc (eventually such extra
cheeks on integer operations can disabled by the -release compilation flag).
 
I don't understand polysemous values as they are explained. In particular, if it is used in a context where sign doesn't matter, it compiles without error. In that case, how does the compiler choose whether it is uint or int? Doesn't the choice matter in any context, because the types overflow at different places? And I would have thought that, if the context clarifies the sign, then the type should just accept the sign determined by the context, ie: void foo(int i) {...} unittest { uint u; int i; auto x = i + u; foo(x); // ok, so x is an int } It seems like this is a more powerful form of type inference, but then what is this about polysemous function results? Quote, "result type or error type." Are polysemous types actually algebraic data types (aka discriminating unions)? I was going to wait for the video and hope that explained it, but now that we're talking...
 
 Page 33: I think some of those "optimizations based on constant folding" can
be done from just a single function (that the compiler automatically transforms
into a kind of "template", copying it as necessary), using some partial
compilation on a copy of the function itself. So a function like:
 int foo(int x, int y) {...}
 If called like:
 a = foo(something(), y);
 b = foo(3, y);
 Can be automatically managed by the compiler as it was a pair of:
 int foo(int x, int y) {...}
 int foo(int x)(int y) {...}
 
 
 Page 52: can't the compiler automatically unroll little loops based on
constant intervals/loop vars? (another compilation flag like -unroll can added,
if necessary). I presume "static foreach" is a way for the programmer to tell
the compiler that he/she really wants such unrolling done, so if such unrolling
isn't possible, then the compiler must raise a compilation error instead of
just silently leave the loop unrolled. At the moment I think tuples are looped
with a implicitly static foreach. Is such implicit "static" going to become
explicit and necessary? So this raises a compilation error:
 void foo(Types...)(Types args) {
   foreach (arg; args)
     bar(arg);
 ...
 And you must write:
 void foo(Types...)(Types args) {
   static foreach (arg; args)
     bar(arg);
 ...
In both of these two features, they indeed do seem to be optimizations which the compiler could otherwise do. But I think that's not the point. Their real use comes from the fact that the variables are compile-time variables, which means that they can be used in defining types. Often it is useful to paramaterise a type by an integer; to do this, the integer must be known at compile time. Writing this: int foo(int x) { MyType!(x) t; } void main() { foo(5); } won't work even though x is known to be 5 at compile-time, as the information of this knowledge isn't transmitted through foo's interface. Static parameters solve this problem. Admittedly, it could be solved by making x a template parameter, but there are reasons not to. Consider the more useful example of a modified writefln. You might have two prototypes: writefln(...); // runtime varargs writefln(static string s, ParamTypes!(s)); // compile-time varargs, with types checked at compile-time by parsing the formatting string This still *could* be done automagically by the compiler, but less likely so. I agree that it would be nice if the compiler did this as an optimization, though. -- Reiner PS. The other related thing I would really like to see is the ability to manipulate types with standard (ie non-template) syntax. The problem at the moment is that you can't use CTFE when doing operations on types. What I would like is some type, for instance Type, which you could use in normal parameter lists. It would always have to be a static parameter, but it could be manipulated with arrays, etc. Then, template Map(alias Fn, T...) { static if (T.length == 0) alias Tuple!() Map; else alias Tuple!(Fn!(T[0]).val, Map!(Fn, T[1..$])) Map; } would become Type[] map(alias Fn)(static Type[] types) { Type[] res; foreach (t; types) res ~= Fn!(t); return res; } I know the details are missing, but I live in hope. :-)
Aug 29 2007
next sibling parent reply Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 PS. The other related thing I would really like to see is the ability to 
 manipulate types with standard (ie non-template) syntax. The problem at 
 the moment is that you can't use CTFE when doing operations on types. 
 What I would like is some type, for instance Type, which you could use 
 in normal parameter lists. It would always have to be a static 
 parameter, but it could be manipulated with arrays, etc. Then,
 
 template Map(alias Fn, T...)
 {
     static if (T.length == 0)
        alias Tuple!() Map;
     else
        alias Tuple!(Fn!(T[0]).val, Map!(Fn, T[1..$])) Map;
 }
 
 would become
 
 Type[] map(alias Fn)(static Type[] types)
 {
     Type[] res;
     foreach (t; types)
         res ~= Fn!(t);
     return res;
 }
 
 I know the details are missing, but I live in hope. :-)
I think I may have accidentally explained the use of static foreach here. <g> This seems like a perfect candidate for static foreach, as well as explaining where static foreach is useful: where the loop *absolutely* *must* be unrolled since the semantics may be different for the different bodies (given that the types are different, semantic analysis must be done multiple times). With static foreach, it might be template Map(alias Fn, Types...) { alias Tuple!() Map; static foreach (T; Types) alias Tuple!(Map, Fn!(T)) Map; } although this relies on the dubious feature of redefining Map... -- Reiner
Aug 29 2007
parent Lars Noschinski <lars-2006-1 usenet.noschinski.de> writes:
* Reiner Pope <some address.com> [07-08-29 10:51]:
This seems like a perfect candidate for static foreach, as well as explaining 
where static foreach is useful: where the loop *absolutely* *must* be unrolled 
since the semantics may be different for the different bodies (given that the 
types are different, semantic analysis must be done multiple times).
If static foreach is going to be like static if, then static foreach will also not create a new nested scope.
Aug 29 2007
prev sibling next sibling parent Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Reiner Pope wrote:
 Consider the more useful example of a modified writefln. You might have two 
 prototypes:

 writefln(...); // runtime varargs
 writefln(static string s, ParamTypes!(s)); // compile-time varargs, with
 types checked at compile-time by parsing the formatting string


 This still *could* be done automagically by the compiler, but less likely so.
 I agree that it would be nice if the compiler did this as an optimization,
 though.
Good example, made me realize that it's more than just syntactic sugar for template parameters. Well, actually it's not, but the sugar is at the call site as well, which means you don't have to know about it to use it. That's the key point, IMO. -- Remove ".doesnotlike.spam" from the mail address.
Aug 29 2007
prev sibling parent bearophile <bearophileHUGS lycos.com> writes:
Reiner Pope:

Presumably just an annotation like "pure" on the function should be enough:<
I don't know if the compiler is able to enforce this, I hope so, otherwise it may become a source of bugs. -------------------------- I have seen they are talking about those D future slides on the very good Lambda the ultimate Blog: http://lambda-the-ultimate.org/node/2421 Three of those notes:
[D is] Not quite functional either. D's closures are broken.
The environment of a nested function points to the outer function's stack and continues to do so after the outer function has returned. When a dynamic closure is called, its vars may refer to a now defunct part of the stack (or worse, belonging to some other call). Horrors. Unfortunately, it isn't merely a problem with the implementation; it is explicitly mentioned in the language reference. I like many of the language's features. However, looking at the slides, I can't escape the feeling that this is another Groovy-style language kitchen sink in the making.< Q: >Is a reson for this design choice given, or is this simply an error that worked its way into the specification?< A: >No, there are no reasons given. I have the utmost respect for Walter Bright. I suspect, however, that the essential mindset behind the requirements and design is that one shouldn't lose any performance ground to C/C++, and it would be undesirable to go to the heap to allocate a closure. All speculations, of course. The D folks have run into this problem already and it may be fixed.< I can see some "solutions": 1) Do nothing. D is a system programming language that tries to be close to the metal as C, so the programmer has to accept some compromises to be able to write fast code. 2) D has "function" and "delegate", so maybe a third annotation can be used to tell the compiler to use a differen semantics, so such functions can be passed around like closures (like you do in Scheme). 3) Global (top) functions and nested functions can become even more different. So if the programmer wants C-like speeds shi/he/she has to use only global functions (like in a C program), if she/he/shi wants to use nested functions (that allow the creation of closures too) then he/she/shi has to accept to go slower. Regarding the "kitchen sink" problem: I think it's not a problem, it's a positive point. It's better to solve a problem using in a shallow way one of many tools than using in deep way one of few powerful and general tools. Because in the fist case the programmer needs less brain, can produce the code faster, and it's less likely to put bugs in (if the libraries are bug-free enough). So I appreciate the style of D of putting lot of things in. I'd even like to see more built-in stuff, like sets, dynamic multidimensional matrixes contiguously allocated, yield, lazy constraint-based programming, and more features for parallel programming taken from Fortress language designed by Sun. :-) Bye and thank you, bearophile
Aug 31 2007