www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Getting the error from __traits(compiles, ...)

reply Bill Baxter <wbaxter gmail.com> writes:
We can pretty much already use __traits(compiles,{...}) to implement
static interface checking.

template checkInterface(T) {
    enum bool checkInterface =
    __traits(compiles,
    {
       T x;
       // some code exercising various aspects of the interface
    });
}

The main problem with this is that a static "implements interface"
check is done like so:

   static assert(checkInterface!(myType));

And all it tells you is a single yes or no.  It would help in
debugging if you could somehow get the reason for the failure.

So how to do it?  All that comes to mind is something like the evil
Errno from C.  A global that gets set by the last failure.
Let's call it __errmsg, but it could be a pragma, or a __traits thing.
 If you had that then you could write this:

   assertImplements!(checkInterface!(myType));

With:
template assertImplements(bool v)
{
       static if (!v) {
            pragma(msg, __errmsg);
            static assert(false);
       }
}

There are lots of ways you could provide such a concept-checker, but
they all require the basic ability to get the reason for the
__traits(compiles) failure.

Any other thoughts about how to get the failure info?   This is
probably the main complaint against __traits(compiles), that there's
no way to find out what went wrong if the code doesn't compile.  Often
it can just be a typo.  I know I've spent plenty of time looking at
static if(__traits(compiles, ...)) checks that weren't working only to
discover I switched an x for a y somewhere.  Or passed the wrong
number of arguments.

--bb
Nov 12 2009
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Bill Baxter wrote:
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.

I agree it's a problem. Perhaps we can do: __traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.
Nov 12 2009
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Walter Bright wrote:
 Bill Baxter wrote:
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.

I agree it's a problem. Perhaps we can do: __traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.

Eh, scratch that dumb idea. Just remove the __traits(compiles, ...) and replace it with ..., and you'll get the message!
Nov 12 2009
next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Denis Koroskin wrote:
 He was asking for a string so that it would be possible to parse and/or 
 output. More like:
 
 enum s = __traits(compiles_or_msg, ...);
 static if (s.length == 0) {
    // no error
 } else {
    // do stuff with error message
 }

Makes sense.
Nov 12 2009
prev sibling parent reply Yigal Chripun <yigal100 gmail.com> writes:
Bill Baxter wrote:
 On Thu, Nov 12, 2009 at 1:00 PM, Walter Bright
 <newshound1 digitalmars.com> wrote:
 Walter Bright wrote:
 Bill Baxter wrote:
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.

__traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.

replace it with ..., and you'll get the message!

Maybe that is enough combined with a const code snippet enum code = q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range } static if (__traits(compiles, mixin(code))) { mixin(code); } else { pragma(msg, "Unable to instantiate code for type T=`"~T.stringof~"`:\n "~ code); pragma(msg, "Compiler reports:" ); mixin(code); } But I was really hoping for a separation of Interface definition and Interface verification. With the above you'll have to have two templates for every interface, like isForwardRange!(T) (evals to bool) and assertIsForwardRange!(T) (reports the compiler error or is silent). Hmm.... unless template assertIsInputRange(T, bool noisy=true) { enum code = q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range }; static if (!__traits(compiles, mixin(code))) { static if (noisy) { pragma(msg, "Type T=`"~T.stringof~"` doesn't support interface:\n "~ code); pragma(msg, "Compiler reports:" ); } mixin(code); } } template isInputRange(T) { enum bool isInputRange = __traits(compiles, assertIsInputRange!(T, false)); } And then we could wrap the whole shebang in a fancy code-generating string mixin and define things like the above using: mixin(DefineInterface( "InputRange", q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range })); mixin(DefineInterface!(assertIsInputRange)( "ForwardRange", q{ R r1; R r2 = r1; // can copy a range object }))); Writing DefineInterface is left as an exercise for the reader. :-) But it looks do-able. And DefineInterface could take a variadic list of assertIsXXX template aliases and generate code to check each one. --bb

I really wish this was folded into the language by allowing structs to implement interfaces. interface Range(T) { bool empty(); void popFront(); T front(); } struct MyRange(T) : Range!(T) { ... } // checked by compiler
Nov 12 2009
parent reply grauzone <none example.net> writes:
Yigal Chripun wrote:
 Bill Baxter wrote:
 On Thu, Nov 12, 2009 at 1:00 PM, Walter Bright
 <newshound1 digitalmars.com> wrote:
 Walter Bright wrote:
 Bill Baxter wrote:
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.

__traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.

replace it with ..., and you'll get the message!

Maybe that is enough combined with a const code snippet enum code = q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range } static if (__traits(compiles, mixin(code))) { mixin(code); } else { pragma(msg, "Unable to instantiate code for type T=`"~T.stringof~"`:\n "~ code); pragma(msg, "Compiler reports:" ); mixin(code); } But I was really hoping for a separation of Interface definition and Interface verification. With the above you'll have to have two templates for every interface, like isForwardRange!(T) (evals to bool) and assertIsForwardRange!(T) (reports the compiler error or is silent). Hmm.... unless template assertIsInputRange(T, bool noisy=true) { enum code = q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range }; static if (!__traits(compiles, mixin(code))) { static if (noisy) { pragma(msg, "Type T=`"~T.stringof~"` doesn't support interface:\n "~ code); pragma(msg, "Compiler reports:" ); } mixin(code); } } template isInputRange(T) { enum bool isInputRange = __traits(compiles, assertIsInputRange!(T, false)); } And then we could wrap the whole shebang in a fancy code-generating string mixin and define things like the above using: mixin(DefineInterface( "InputRange", q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h = r.front; // can get the front of the range })); mixin(DefineInterface!(assertIsInputRange)( "ForwardRange", q{ R r1; R r2 = r1; // can copy a range object }))); Writing DefineInterface is left as an exercise for the reader. :-) But it looks do-able. And DefineInterface could take a variadic list of assertIsXXX template aliases and generate code to check each one. --bb

I really wish this was folded into the language by allowing structs to implement interfaces. interface Range(T) { bool empty(); void popFront(); T front(); } struct MyRange(T) : Range!(T) { ... } // checked by compiler

One problem with this was that arrays wouldn't automagically be ranges anymore. Right now, "int[] a; a.popFront();" works, because std.array has a global function popFront. Some old language hack turns a.popFront into popFront(a).
Nov 13 2009
parent reply Yigal Chripun <yigal100 gmail.com> writes:
grauzone wrote:
 Yigal Chripun wrote:
 I really wish this was folded into the language by allowing structs to 
 implement interfaces.

 interface Range(T) {
   bool empty();
   void popFront();
   T front();
 }

 struct MyRange(T) : Range!(T) { ... } // checked by compiler

One problem with this was that arrays wouldn't automagically be ranges anymore. Right now, "int[] a; a.popFront();" works, because std.array has a global function popFront. Some old language hack turns a.popFront into popFront(a).

you're talking about this construct: int[] arr; void foo(int[], params) {} => arr.foo(params); This should not be considered a hack but rather should be a feature extended for all types. see extension methods in C#. I also think that arrays (containers) must be distinct from slices/ranges (views).
Nov 13 2009
parent grauzone <none example.net> writes:
Yigal Chripun wrote:
 grauzone wrote:
 Yigal Chripun wrote:
 I really wish this was folded into the language by allowing structs 
 to implement interfaces.

 interface Range(T) {
   bool empty();
   void popFront();
   T front();
 }

 struct MyRange(T) : Range!(T) { ... } // checked by compiler

One problem with this was that arrays wouldn't automagically be ranges anymore. Right now, "int[] a; a.popFront();" works, because std.array has a global function popFront. Some old language hack turns a.popFront into popFront(a).

you're talking about this construct: int[] arr; void foo(int[], params) {} => arr.foo(params); This should not be considered a hack but rather should be a feature extended for all types. see extension methods in C#.

I'd rather have some sort of open types, which are the clean version of this hack.
 I also think that arrays (containers) must be distinct from 
 slices/ranges (views).

Nov 13 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Fri, 13 Nov 2009 00:00:42 +0300, Walter Bright  
<newshound1 digitalmars.com> wrote:

 Walter Bright wrote:
 Bill Baxter wrote:
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.

__traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.

Eh, scratch that dumb idea. Just remove the __traits(compiles, ...) and replace it with ..., and you'll get the message!

He was asking for a string so that it would be possible to parse and/or output. More like: enum s = __traits(compiles_or_msg, ...); static if (s.length == 0) { // no error } else { // do stuff with error message }
Nov 12 2009
prev sibling next sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Thu, Nov 12, 2009 at 1:00 PM, Walter Bright
<newshound1 digitalmars.com> wrote:
 Walter Bright wrote:
 Bill Baxter wrote:
 Any other thoughts about how to get the failure info? =A0 This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile. =A0Ofte=



 it can just be a typo. =A0I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere. =A0Or passed the wrong
 number of arguments.

I agree it's a problem. Perhaps we can do: =A0 __traits(compiles_or_msg, ...) which would print the error messages, at least making it easier to track down.

Eh, scratch that dumb idea. Just remove the __traits(compiles, ...) and replace it with ..., and you'll get the message!

Maybe that is enough combined with a const code snippet enum code =3D q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h =3D r.front; // can get the front of the range } static if (__traits(compiles, mixin(code))) { mixin(code); } else { pragma(msg, "Unable to instantiate code for type T=3D`"~T.stringof~"`:\n "~ code); pragma(msg, "Compiler reports:" ); mixin(code); } But I was really hoping for a separation of Interface definition and Interface verification. With the above you'll have to have two templates for every interface, like isForwardRange!(T) (evals to bool) and assertIsForwardRange!(T) (reports the compiler error or is silent). Hmm.... unless template assertIsInputRange(T, bool noisy=3Dtrue) { enum code =3D q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h =3D r.front; // can get the front of the range }; static if (!__traits(compiles, mixin(code))) { static if (noisy) { pragma(msg, "Type T=3D`"~T.stringof~"` doesn't support interface:\n "~ code); pragma(msg, "Compiler reports:" ); } mixin(code); } } template isInputRange(T) { enum bool isInputRange =3D __traits(compiles, assertIsInputRange!(T, f= alse)); } And then we could wrap the whole shebang in a fancy code-generating string mixin and define things like the above using: mixin(DefineInterface( "InputRange", q{ R r; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke next auto h =3D r.front; // can get the front of the range })); mixin(DefineInterface!(assertIsInputRange)( "ForwardRange", q{ R r1; R r2 =3D r1; // can copy a range object }))); Writing DefineInterface is left as an exercise for the reader. :-) But it looks do-able. And DefineInterface could take a variadic list of assertIsXXX template aliases and generate code to check each one. --bb
Nov 12 2009
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
Bill Baxter wrote:
 We can pretty much already use __traits(compiles,{...}) to implement
 static interface checking.
 
 template checkInterface(T) {
     enum bool checkInterface =
     __traits(compiles,
     {
        T x;
        // some code exercising various aspects of the interface
     });
 }
 
 The main problem with this is that a static "implements interface"
 check is done like so:
 
    static assert(checkInterface!(myType));
 
 And all it tells you is a single yes or no.  It would help in
 debugging if you could somehow get the reason for the failure.
 
 So how to do it?  All that comes to mind is something like the evil
 Errno from C.  A global that gets set by the last failure.
 Let's call it __errmsg, but it could be a pragma, or a __traits thing.
  If you had that then you could write this:
 
    assertImplements!(checkInterface!(myType));
 
 With:
 template assertImplements(bool v)
 {
        static if (!v) {
             pragma(msg, __errmsg);
             static assert(false);
        }
 }
 
 There are lots of ways you could provide such a concept-checker, but
 they all require the basic ability to get the reason for the
 __traits(compiles) failure.
 
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.
 
 --bb

I think you might be asking for: static try { xxx; } catch( CompilerError[] errors){ pragma(msg, "Failure in Frobozz!"); pragma(msg, errors[0].msg); }
Nov 13 2009
next sibling parent Justin Johansson <free beer.com> writes:
 There are lots of ways you could provide such a concept-checker, but
 they all require the basic ability to get the reason for the
 __traits(compiles) failure.
 
 Any other thoughts about how to get the failure info?   This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile.  Often
 it can just be a typo.  I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere.  Or passed the wrong
 number of arguments.
 
 --bb

I think you might be asking for: static try { xxx; } catch( CompilerError[] errors){ pragma(msg, "Failure in Frobozz!"); pragma(msg, errors[0].msg); }

This is very interesting. Given previous/recent discussion about scopes, should there be, or are there, analogous static scope statements so that one could make use of scope(exit), scope(failure), et. al. in a static context? beers, Justin
Nov 13 2009
prev sibling next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Don:

 I think you might be asking for:
 
 static try {
     xxx;
 }
 catch( CompilerError[] errors){
     pragma(msg, "Failure in Frobozz!");
     pragma(msg, errors[0].msg);
 }

Good. I'd like to have a handy&clean way to verify that some static asserts throw inside my unit tests. Bye, bearophile
Nov 13 2009
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Bill Baxter:
 2) how to get and report errors related to failure to compile some
 code. (this one I hadn't thought of back then)

I'd like a "static foreach" too. Eventually most statements will have a static version. At that point people will start seeing this little duplication in the language and someone may invent a way to throw away all the static versions and allow normal D code to be used at compile time, maybe with a 2-stage compilation or something. Design is often like that, with phases of expansion of functionality followed by phases of synthesis and generalization. Bye, bearophile
Nov 13 2009
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 8:40 AM, bearophile <bearophileHUGS lycos.com> wrote:
 Bill Baxter:
 2) how to get and report errors related to failure to compile some
 code. (this one I hadn't thought of back then)


A static switch would be nice too. static if (is(type == xxx)) {} else static if (is(type==yyy)) {} else static if ... gets kinda tedious. The kind of unification you're talking about is one thing I like about Nemerle's 2-phase macros-as-plugins. The code you execute at compile time is written in exactly the same language as what you execute at runtime. And no CTFE engine is required to make it work. Only one new construct required, the macro facility itself. But I don't think that leads to elimination static if, etc. It just means that such things become implementable as macros, rather than language constructs. On the other hand, having macros doesn't mean that you don't want constant folding. And if you can fold the constant 2+3, why not the constant add(2,3)? So desire to fold as many constants as possible naturally leads to a desire to do CTFE and be able to execute your entire language at compile time. And once you're there -- yeh, I guess you're right. Ultimately it's not really necessary to specify static if vs regular if. It's yet another extension of constant folding -- if the condition is a compile time constant, then it can act as a static if. Same goes for loops. But like regular loop unrolling optimizations, the compiler should decide if it's prudent to unroll that 10,000 static foreach loop or not. So in short. I think you're right. "static if" should go away. But "2-stage" compilation isn't really necessary, just more extensions to the constant folding engine. (Or perhaps you could say constant folding is already a separate stage of a 2-stage process)

"static if" is different from "if" in the way it handles scopes. Also, the former is a declaration, the latter is a statement. Andrei
Nov 13 2009
prev sibling parent reply Yigal Chripun <yigal100 gmail.com> writes:
On 13/11/2009 19:30, Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 8:40 AM, bearophile<bearophileHUGS lycos.com>
 wrote:
 Bill Baxter:
 2) how to get and report errors related to failure to compile
 some code. (this one I hadn't thought of back then)

I'd like a "static foreach" too. Eventually most statements will have a static version. At that point people will start seeing this little duplication in the language and someone may invent a way to throw away all the static versions and allow normal D code to be used at compile time, maybe with a 2-stage compilation or something.

A static switch would be nice too. static if (is(type == xxx)) {} else static if (is(type==yyy)) {} else static if ... gets kinda tedious. The kind of unification you're talking about is one thing I like about Nemerle's 2-phase macros-as-plugins. The code you execute at compile time is written in exactly the same language as what you execute at runtime. And no CTFE engine is required to make it work. Only one new construct required, the macro facility itself. But I don't think that leads to elimination static if, etc. It just means that such things become implementable as macros, rather than language constructs. On the other hand, having macros doesn't mean that you don't want constant folding. And if you can fold the constant 2+3, why not the constant add(2,3)? So desire to fold as many constants as possible naturally leads to a desire to do CTFE and be able to execute your entire language at compile time. And once you're there -- yeh, I guess you're right. Ultimately it's not really necessary to specify static if vs regular if. It's yet another extension of constant folding -- if the condition is a compile time constant, then it can act as a static if. Same goes for loops. But like regular loop unrolling optimizations, the compiler should decide if it's prudent to unroll that 10,000 static foreach loop or not. So in short. I think you're right. "static if" should go away. But "2-stage" compilation isn't really necessary, just more extensions to the constant folding engine. (Or perhaps you could say constant folding is already a separate stage of a 2-stage process) --bb

I don't follow your logic regarding CTFE. with 2 phase macros a-la nemerle: macro foo() { int res = 2 + 3; return res; } macro bar() { return q{2 + 3}; } foo's addition is done at compile time so the constant folding was implemented in the macro body bar return the AST for the expression "2 + 3". Compiler optimizations like constant folding will apply just as if you wrote that expression yourself instead of generating it by calling a macro. static if is not supposed to be implemented with macros, rather the equivalent of a static if would be using a regular if *inside* the body of the macro.
Nov 13 2009
parent reply Yigal Chripun <yigal100 gmail.com> writes:
On 13/11/2009 22:05, Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 11:50 AM, Yigal Chripun<yigal100 gmail.com>  wrote:
 I don't follow your logic regarding CTFE.

 with 2 phase macros a-la nemerle:

 macro foo() {
   int res = 2 + 3;
   return res;
 }

 macro bar() {
   return q{2 + 3};
 }

 foo's addition is done at compile time so the constant folding was
 implemented in the macro body

 bar return the AST for the expression "2 + 3". Compiler optimizations like
 constant folding will apply just as if you wrote that expression yourself
 instead of generating it by calling a macro.

Right, which is why I'm saying you still want constant folding/CTFE even if you have a macro system. But then if you're going to have CTFE sitting around anyway, you might as well use it to implement macros instead of going to this funky two-phase thing. That was one point I was making, though not so clearly.
 static if is not supposed to be implemented with macros, rather the
 equivalent of a static if would be using a regular if *inside* the body of
 the macro.

But that's how you would implement a static if with macros, no? (pardon the incorrect nemerle syntax) macro static_if(bool cond, a, b) { if (cond) { <| a |> } else { <| b |> } } --bb

let's start from the end, yes, you can implement static if that way but I don't see why would you want to do that. regarding constant folding and 2 phase compilation: from what I know, DMD currently contains inside two backends, the regular one generates the executable *and* an interpreter which does CTFE. Constant folding is an optimization used by both. basically we already have two phases of compilation but they are done internally by DMD. This means that DMD has two separate backends instead of just one and that you need separate syntax to target the different phases. The major problems I see with the current system: - unnecessary duplication of syntax - two backends instead of just one which complicates the compiler implementation - unclear separation of phase in code: auto arr = [1.0, 2.0, bar(5.0)]; // when is bar executed? also, how can I control when it's executed? This is needlessly confusing and doesn't provide enough control to the programmer. it's analogous to structs vs. classes. I'm sure everyone in the D community agree that this separation of concerns is much better than the bug-prone c++ way.
Nov 14 2009
parent reply Don <nospam nospam.com> writes:
Yigal Chripun wrote:
 On 13/11/2009 22:05, Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 11:50 AM, Yigal Chripun<yigal100 gmail.com>  
 wrote:
 I don't follow your logic regarding CTFE.

 with 2 phase macros a-la nemerle:

 macro foo() {
   int res = 2 + 3;
   return res;
 }

 macro bar() {
   return q{2 + 3};
 }

 foo's addition is done at compile time so the constant folding was
 implemented in the macro body

 bar return the AST for the expression "2 + 3". Compiler optimizations 
 like
 constant folding will apply just as if you wrote that expression 
 yourself
 instead of generating it by calling a macro.

Right, which is why I'm saying you still want constant folding/CTFE even if you have a macro system. But then if you're going to have CTFE sitting around anyway, you might as well use it to implement macros instead of going to this funky two-phase thing. That was one point I was making, though not so clearly.
 static if is not supposed to be implemented with macros, rather the
 equivalent of a static if would be using a regular if *inside* the 
 body of
 the macro.

But that's how you would implement a static if with macros, no? (pardon the incorrect nemerle syntax) macro static_if(bool cond, a, b) { if (cond) { <| a |> } else { <| b |> } } --bb

let's start from the end, yes, you can implement static if that way but I don't see why would you want to do that. regarding constant folding and 2 phase compilation: from what I know, DMD currently contains inside two backends, the regular one generates the executable *and* an interpreter which does CTFE

 Constant folding is an optimization used by both.
 
 basically we already have two phases of compilation but they are done 
 internally by DMD. This means that DMD has two separate backends instead 
 of just one and that you need separate syntax to target the different 
 phases.
 
 The major problems I see with the current system:
 - unnecessary duplication of syntax
 - two backends instead of just one which complicates the compiler 
 implementation

There's only one backend. The interpreter is basically just constant folding, with a small amount of interpreting of statements. 90% of the CTFE complexity is in the constant folding. The interpreter itself is only about 200 lines of code. But the real backend is huge.
 - unclear separation of phase in code:
   auto arr = [1.0, 2.0, bar(5.0)]; // when is bar executed?
 also, how can I control when it's executed? This is needlessly confusing 
 and doesn't provide enough control to the programmer.

That particular example is caused by array literals not being immutable, which I am certain is a mistake.
 it's analogous to structs vs. classes. I'm sure everyone in the D 
 community agree that this separation of concerns is much better than the 
 bug-prone c++ way.

Nov 14 2009
parent Yigal Chripun <yigal100 gmail.com> writes:
On 14/11/2009 13:32, Don wrote:
 Yigal Chripun wrote:
 On 13/11/2009 22:05, Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 11:50 AM, Yigal Chripun<yigal100 gmail.com>
 wrote:
 I don't follow your logic regarding CTFE.

 with 2 phase macros a-la nemerle:

 macro foo() {
 int res = 2 + 3;
 return res;
 }

 macro bar() {
 return q{2 + 3};
 }

 foo's addition is done at compile time so the constant folding was
 implemented in the macro body

 bar return the AST for the expression "2 + 3". Compiler
 optimizations like
 constant folding will apply just as if you wrote that expression
 yourself
 instead of generating it by calling a macro.

Right, which is why I'm saying you still want constant folding/CTFE even if you have a macro system. But then if you're going to have CTFE sitting around anyway, you might as well use it to implement macros instead of going to this funky two-phase thing. That was one point I was making, though not so clearly.
 static if is not supposed to be implemented with macros, rather the
 equivalent of a static if would be using a regular if *inside* the
 body of
 the macro.

But that's how you would implement a static if with macros, no? (pardon the incorrect nemerle syntax) macro static_if(bool cond, a, b) { if (cond) { <| a |> } else { <| b |> } } --bb

let's start from the end, yes, you can implement static if that way but I don't see why would you want to do that. regarding constant folding and 2 phase compilation: from what I know, DMD currently contains inside two backends, the regular one generates the executable *and* an interpreter which does CTFE

 Constant folding is an optimization used by both.

 basically we already have two phases of compilation but they are done
 internally by DMD. This means that DMD has two separate backends
 instead of just one and that you need separate syntax to target the
 different phases.

 The major problems I see with the current system:
 - unnecessary duplication of syntax
 - two backends instead of just one which complicates the compiler
 implementation

There's only one backend. The interpreter is basically just constant folding, with a small amount of interpreting of statements. 90% of the CTFE complexity is in the constant folding. The interpreter itself is only about 200 lines of code. But the real backend is huge.
 - unclear separation of phase in code:
 auto arr = [1.0, 2.0, bar(5.0)]; // when is bar executed?
 also, how can I control when it's executed? This is needlessly
 confusing and doesn't provide enough control to the programmer.

That particular example is caused by array literals not being immutable, which I am certain is a mistake.
 it's analogous to structs vs. classes. I'm sure everyone in the D
 community agree that this separation of concerns is much better than
 the bug-prone c++ way.


Don, what's your opinion regarding two phase compilation a-la Nemerle vs. the current D model? btw, another benefit I forgot to mention regarding this is that in Nemerle compile time code is precompiled which solves a lot of problems with c++ style template code
Nov 14 2009
prev sibling next sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Fri, Nov 13, 2009 at 12:09 AM, Don <nospam nospam.com> wrote:
 Bill Baxter wrote:
 We can pretty much already use __traits(compiles,{...}) to implement
 static interface checking.

 template checkInterface(T) {
 =A0 =A0enum bool checkInterface =3D
 =A0 =A0__traits(compiles,
 =A0 =A0{
 =A0 =A0 =A0 T x;
 =A0 =A0 =A0 // some code exercising various aspects of the interface
 =A0 =A0});
 }

 The main problem with this is that a static "implements interface"
 check is done like so:

 =A0 static assert(checkInterface!(myType));

 And all it tells you is a single yes or no. =A0It would help in
 debugging if you could somehow get the reason for the failure.

 So how to do it? =A0All that comes to mind is something like the evil
 Errno from C. =A0A global that gets set by the last failure.
 Let's call it __errmsg, but it could be a pragma, or a __traits thing.
 =A0If you had that then you could write this:

 =A0 assertImplements!(checkInterface!(myType));

 With:
 template assertImplements(bool v)
 {
 =A0 =A0 =A0 static if (!v) {
 =A0 =A0 =A0 =A0 =A0 =A0pragma(msg, __errmsg);
 =A0 =A0 =A0 =A0 =A0 =A0static assert(false);
 =A0 =A0 =A0 }
 }

 There are lots of ways you could provide such a concept-checker, but
 they all require the basic ability to get the reason for the
 __traits(compiles) failure.

 Any other thoughts about how to get the failure info? =A0 This is
 probably the main complaint against __traits(compiles), that there's
 no way to find out what went wrong if the code doesn't compile. =A0Often
 it can just be a typo. =A0I know I've spent plenty of time looking at
 static if(__traits(compiles, ...)) checks that weren't working only to
 discover I switched an x for a y somewhere. =A0Or passed the wrong
 number of arguments.

 --bb

I think you might be asking for: static try { =A0 xxx; } catch( CompilerError[] errors){ =A0 pragma(msg, "Failure in Frobozz!"); =A0 pragma(msg, errors[0].msg); }

Heh heh. Well I proposed something along those lines before, and it didn't catch on, so I decided to avoid mentioning it this time. :-) http://digitalmars.com/d/archives/digitalmars/D/static_try_catch_construct_= would_be_helpful_66794.html But yeh, actually now that you mention it that handles both problems I brought up recently. 1) how to run code only if it compiles and avoid repeating yourself. (the original reason I proposed it) 2) how to get and report errors related to failure to compile some code. (this one I hadn't thought of back then) But yeh, it looks like that could solve both problems. --bb
Nov 13 2009
prev sibling next sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Fri, Nov 13, 2009 at 8:40 AM, bearophile <bearophileHUGS lycos.com> wrot=
e:
 Bill Baxter:
 2) how to get and report errors related to failure to compile some
 code. (this one I hadn't thought of back then)

I'd like a "static foreach" too. Eventually most statements will have a s=

on in the language and someone may invent a way to throw away all the stati= c versions and allow normal D code to be used at compile time, maybe with a= 2-stage compilation or something. A static switch would be nice too. static if (is(type =3D=3D xxx)) {} else static if (is(type=3D=3Dyyy)) {} else static if ... gets kinda tedious. The kind of unification you're talking about is one thing I like about Nemerle's 2-phase macros-as-plugins. The code you execute at compile time is written in exactly the same language as what you execute at runtime. And no CTFE engine is required to make it work. Only one new construct required, the macro facility itself. But I don't think that leads to elimination static if, etc. It just means that such things become implementable as macros, rather than language constructs. On the other hand, having macros doesn't mean that you don't want constant folding. And if you can fold the constant 2+3, why not the constant add(2,3)? So desire to fold as many constants as possible naturally leads to a desire to do CTFE and be able to execute your entire language at compile time. And once you're there -- yeh, I guess you're right. Ultimately it's not really necessary to specify static if vs regular if. It's yet another extension of constant folding -- if the condition is a compile time constant, then it can act as a static if. Same goes for loops. But like regular loop unrolling optimizations, the compiler should decide if it's prudent to unroll that 10,000 static foreach loop or not. So in short. I think you're right. "static if" should go away. But "2-stage" compilation isn't really necessary, just more extensions to the constant folding engine. (Or perhaps you could say constant folding is already a separate stage of a 2-stage process) --bb
Nov 13 2009
prev sibling next sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Fri, Nov 13, 2009 at 9:38 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 8:40 AM, bearophile <bearophileHUGS lycos.com>
 wrote:
 Bill Baxter:
 2) how to get and report errors related to failure to compile some
 code. (this one I hadn't thought of back then)

I'd like a "static foreach" too. Eventually most statements will have a static version. At that point people will start seeing this little duplication in the language and someone may invent a way to throw away =



 the static versions and allow normal D code to be used at compile time,
 maybe with a 2-stage compilation or something.

A static switch would be nice too. =A0 static if (is(type =3D=3D xxx)) {=


 else static if (is(type=3D=3Dyyy)) {} else static if ... gets kinda
 tedious.


 The kind of unification you're talking about is one thing I like about
 Nemerle's 2-phase macros-as-plugins. =A0The code you execute at compile
 time is written in exactly the same language as what you execute at
 runtime. =A0And no CTFE engine is required to make it work. =A0Only one
 new construct required, the macro facility itself.

 But I don't think that leads to elimination static if, etc. =A0It just
 means that such things become implementable as macros, rather than
 language constructs.

 On the other hand, having macros doesn't mean that you don't want
 constant folding. =A0And if you can fold the constant 2+3, why not the
 constant add(2,3)? =A0So desire to fold as many constants as possible
 naturally leads to a desire to do CTFE and be able to execute your
 entire language at compile time.

 And once you're there -- yeh, I guess you're right. =A0 Ultimately it's
 not really necessary to specify static if vs regular if. =A0 It's yet
 another extension of constant folding -- if the condition is a compile
 time constant, then it can act as a static if. =A0 Same goes for loops.
 But like regular loop unrolling optimizations, the compiler should
 decide if it's prudent to unroll that 10,000 static foreach loop or
 not.

 So in short. =A0I think you're right. =A0"static if" =A0should go away. =


 "2-stage" compilation isn't really necessary, just more extensions to
 the constant folding engine. =A0(Or perhaps you could say constant
 folding is already a separate stage of a 2-stage process)

"static if" is different from "if" in the way it handles scopes.

Good point. It's not really an essential distinction, though. If the language had no-scope blocks in addition to scope blocks then you could have no-scope dynamic if() also. I've always found it unpleasing anyway that one word (somewhat separated from the {}) changes whether {} creates a scope or not.
 Also, the
 former is a declaration, the latter is a statement.

That mostly just means you can use "static if" at the top level, right? If the language eliminated "static if" then clearly all if's at the top level would be required to have static conditions and use no-scope blocks. But ok. These things probably make it too big a change for D to swallow. --bb
Nov 13 2009
prev sibling parent Bill Baxter <wbaxter gmail.com> writes:
On Fri, Nov 13, 2009 at 11:50 AM, Yigal Chripun <yigal100 gmail.com> wrote:
 On 13/11/2009 19:30, Bill Baxter wrote:
 On Fri, Nov 13, 2009 at 8:40 AM, bearophile<bearophileHUGS lycos.com>
 wrote:
 Bill Baxter:
 2) how to get and report errors related to failure to compile
 some code. (this one I hadn't thought of back then)

I'd like a "static foreach" too. Eventually most statements will have a static version. At that point people will start seeing this little duplication in the language and someone may invent a way to throw away all the static versions and allow normal D code to be used at compile time, maybe with a 2-stage compilation or something.

A static switch would be nice too. =A0 static if (is(type =3D=3D xxx)) {=


 else static if (is(type=3D=3Dyyy)) {} else static if ... gets kinda
 tedious.


 The kind of unification you're talking about is one thing I like
 about Nemerle's 2-phase macros-as-plugins. =A0The code you execute at
 compile time is written in exactly the same language as what you
 execute at runtime. =A0And no CTFE engine is required to make it work.
 Only one new construct required, the macro facility itself.

 But I don't think that leads to elimination static if, etc. =A0It just
 means that such things become implementable as macros, rather than
 language constructs.

 On the other hand, having macros doesn't mean that you don't want
 constant folding. =A0And if you can fold the constant 2+3, why not the
 constant add(2,3)? =A0So desire to fold as many constants as possible
 naturally leads to a desire to do CTFE and be able to execute your
 entire language at compile time.

 And once you're there -- yeh, I guess you're right. =A0 Ultimately
 it's not really necessary to specify static if vs regular if. =A0 It's
 yet another extension of constant folding -- if the condition is a
 compile time constant, then it can act as a static if. =A0 Same goes
 for loops. But like regular loop unrolling optimizations, the
 compiler should decide if it's prudent to unroll that 10,000 static
 foreach loop or not.

 So in short. =A0I think you're right. =A0"static if" =A0should go away.
 But "2-stage" compilation isn't really necessary, just more
 extensions to the constant folding engine. =A0(Or perhaps you could say
 constant folding is already a separate stage of a 2-stage process)

 --bb

I don't follow your logic regarding CTFE. with 2 phase macros a-la nemerle: macro foo() { =A0int res =3D 2 + 3; =A0return res; } macro bar() { =A0return q{2 + 3}; } foo's addition is done at compile time so the constant folding was implemented in the macro body bar return the AST for the expression "2 + 3". Compiler optimizations lik=

 constant folding will apply just as if you wrote that expression yourself
 instead of generating it by calling a macro.

Right, which is why I'm saying you still want constant folding/CTFE even if you have a macro system. But then if you're going to have CTFE sitting around anyway, you might as well use it to implement macros instead of going to this funky two-phase thing. That was one point I was making, though not so clearly.
 static if is not supposed to be implemented with macros, rather the
 equivalent of a static if would be using a regular if *inside* the body o=

 the macro.

But that's how you would implement a static if with macros, no? (pardon the incorrect nemerle syntax) macro static_if(bool cond, a, b) { if (cond) { <| a |> } else { <| b |> } } --bb
Nov 13 2009