www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - UDAs - Restrict to User Defined Types?

reply Walter Bright <newshound2 digitalmars.com> writes:
Started a new thread on this.

On 11/7/2012 3:05 AM, Leandro Lucarella wrote:
 OK, that's another thing. And maybe a reason for listening to people having
 more experience with UDAs than you.

 For me the analogy with Exceptions is pretty good. The issues an conveniences
 of throwing anything or annotating a symbol with anything instead of just
 type are pretty much the same. I only see functions making sense to be accepted
 as annotations too (that's what Python do with annotations,  annotation symbol
 is the same as symbol = annotation(symbol), but is quite a different language).

There's another aspect to this. D's UDAs are a purely compile time system, attaching arbitrary metadata to specific symbols. The other UDA systems I'm aware of appear to be runtime systems. This implies the use cases will be different - how, I don't really know. But I don't know of any other compile time UDA system. Experience with runtime systems may not be as applicable. Another interesting data point is CTFE. C++11 has CTFE, but it was deliberately crippled and burdened with "constexpr". From what I read, this was out of fear that it would turn out to be an overused and overabused feature. Of course, this turned out to be a large error. One last thing. Sure, string attributes can (and surely would be) used for different purposes in different libraries. The presumption is that this would cause a conflict. But would it? There are two aspects to a UDA - the attribute itself, and the symbol it is attached to. In order to get the UDA for a symbol, one has to look up the symbol. There isn't a global repository of symbols in D. You'd have to say "I want to look in module X for symbols." Why would you look in module X for an attribute that you have no reason to believe applies to symbols from X? How would an attribute for module X's symbols leak out of X on their own? It's not quite analogous to exceptions, because arbitrary exceptions thrown from module X can flow through your code even though you have no idea module X even exists.
Nov 07 2012
next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
I think restricting to user types makes sense because the type is 
how you'd actually do anything with it.

My figuring is you'd generally do something like this:


foreach(t; __traits(getAttributes, foo))
    static if(is(t == WhatICareAbout)) {
      // use it
    }



And if what you care about is something like string or int, how 
do you know it semantically means what you think it means?
Nov 07 2012
prev sibling next sibling parent "Tobias Pankrath" <tobias pankrath.net> writes:
On Wednesday, 7 November 2012 at 23:18:41 UTC, Walter Bright 
wrote:
 Why would you look in module X for an attribute that you have 
 no reason to believe applies to symbols from X? How would an 
 attribute for module X's symbols leak out of X on their own?

What if you are using two different libraries in X that require you to annotate something with a string?
Nov 07 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Wednesday, 7 November 2012 at 23:18:41 UTC, Walter Bright 
wrote:
 One last thing. Sure, string attributes can (and surely would 
 be) used for different purposes in different libraries. The 
 presumption is that this would cause a conflict. But would it? 
 There are two aspects to a UDA - the attribute itself, and the 
 symbol it is attached to. In order to get the UDA for a symbol, 
 one has to look up the symbol. There isn't a global repository 
 of symbols in D. You'd have to say "I want to look in module X 
 for symbols." Why would you look in module X for an attribute 
 that you have no reason to believe applies to symbols from X? 
 How would an attribute for module X's symbols leak out of X on 
 their own?

Wasn't the every_body_writes_their_names_like_this in C earlier in this thread yours? The problem is that without resorting to some kind of »unique« prefix convention, there will inevitably be modules X and Y which use the same annotation string for different purposes, creating problems if both modules are used together in the same code base (think passing a symbol from X to a template in Y, or an user wanting to create a type which is passed to both X and Y). I feel this is of particular significance here, as I think attributes are primarily going to be used in this kind of library context, where you allow the library user to mark their symbols up in a way that effects your library's behavior on said symbols. David
Nov 07 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2012-11-08, 00:18, Walter Bright wrote:

 Started a new thread on this.

 On 11/7/2012 3:05 AM, Leandro Lucarella wrote:
  > OK, that's another thing. And maybe a reason for listening to people  
 having
  > more experience with UDAs than you.
  >
  > For me the analogy with Exceptions is pretty good. The issues an  
 conveniences
  > of throwing anything or annotating a symbol with anything instead of  
 just
  > type are pretty much the same. I only see functions making sense to  
 be accepted
  > as annotations too (that's what Python do with annotations,  
  annotation symbol
  > is the same as symbol = annotation(symbol), but is quite a different  
 language).

 There's another aspect to this.

 D's UDAs are a purely compile time system, attaching arbitrary metadata  
 to specific symbols. The other UDA systems I'm aware of appear to be  
 runtime systems.

 This implies the use cases will be different - how, I don't really know.  
 But I don't know of any other compile time UDA system. Experience with  
 runtime systems may not be as applicable.

 Another interesting data point is CTFE. C++11 has CTFE, but it was  
 deliberately crippled and burdened with "constexpr". From what I read,  
 this was out of fear that it would turn out to be an overused and  
 overabused feature. Of course, this turned out to be a large error.

 One last thing. Sure, string attributes can (and surely would be) used  
 for different purposes in different libraries. The presumption is that  
 this would cause a conflict. But would it? There are two aspects to a  
 UDA - the attribute itself, and the symbol it is attached to. In order  
 to get the UDA for a symbol, one has to look up the symbol. There isn't  
 a global repository of symbols in D. You'd have to say "I want to look  
 in module X for symbols." Why would you look in module X for an  
 attribute that you have no reason to believe applies to symbols from X?  
 How would an attribute for module X's symbols leak out of X on their own?

 It's not quite analogous to exceptions, because arbitrary exceptions  
 thrown from module X can flow through your code even though you have no  
 idea module X even exists.

I'm not sure what the correct answer is here, but I think the solutions are either to allow any old type as a UDA, or have attribute structs or something along those lines. Just user defined types is a weird restriction. -- Simen
Nov 07 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/08/2012 12:18 AM, Walter Bright wrote:
 Started a new thread on this.

 On 11/7/2012 3:05 AM, Leandro Lucarella wrote:
  > OK, that's another thing. And maybe a reason for listening to people
 having
  > more experience with UDAs than you.
  >
  > For me the analogy with Exceptions is pretty good. The issues an
 conveniences
  > of throwing anything or annotating a symbol with anything instead of
 just
  > type are pretty much the same. I only see functions making sense to
 be accepted
  > as annotations too (that's what Python do with annotations,
  annotation symbol
  > is the same as symbol = annotation(symbol), but is quite a different
 language).

 There's another aspect to this.

 D's UDAs are a purely compile time system, attaching arbitrary metadata
 to specific symbols. The other UDA systems I'm aware of appear to be
 runtime systems.

 This implies the use cases will be different - how, I don't really know.
 But I don't know of any other compile time UDA system. Experience with
 runtime systems may not be as applicable.

 Another interesting data point is CTFE. C++11 has CTFE, but it was
 deliberately crippled and burdened with "constexpr". From what I read,
 this was out of fear that it would turn out to be an overused and
 overabused feature. Of course, this turned out to be a large error.

 One last thing. Sure, string attributes can (and surely would be) used
 for different purposes in different libraries. The presumption is that
 this would cause a conflict. But would it? There are two aspects to a
 UDA - the attribute itself, and the symbol it is attached to. In order
 to get the UDA for a symbol, one has to look up the symbol. There isn't
 a global repository of symbols in D. You'd have to say "I want to look
 in module X for symbols." Why would you look in module X for an
 attribute that you have no reason to believe applies to symbols from X?
 How would an attribute for module X's symbols leak out of X on their own?

 It's not quite analogous to exceptions, because arbitrary exceptions
 thrown from module X can flow through your code even though you have no
 idea module X even exists.

This is a valid point, and I think it does not really make sense to only exclude built-in types. Any type not intended for use as an attribute and that is exported to sufficiently many places can have the same behaviour. I'd vote no restrictions at all, or for requiring attribute annotations on the user-defined type and ban user-defined types from being annotations that do not have that.
Nov 07 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 08/11/2012 01:11, Timon Gehr a écrit :
 On 11/08/2012 12:18 AM, Walter Bright wrote:
 Started a new thread on this.

 On 11/7/2012 3:05 AM, Leandro Lucarella wrote:
 OK, that's another thing. And maybe a reason for listening to people

 more experience with UDAs than you.

 For me the analogy with Exceptions is pretty good. The issues an

 of throwing anything or annotating a symbol with anything instead of

 type are pretty much the same. I only see functions making sense to

 as annotations too (that's what Python do with annotations,

 is the same as symbol = annotation(symbol), but is quite a different

There's another aspect to this. D's UDAs are a purely compile time system, attaching arbitrary metadata to specific symbols. The other UDA systems I'm aware of appear to be runtime systems. This implies the use cases will be different - how, I don't really know. But I don't know of any other compile time UDA system. Experience with runtime systems may not be as applicable. Another interesting data point is CTFE. C++11 has CTFE, but it was deliberately crippled and burdened with "constexpr". From what I read, this was out of fear that it would turn out to be an overused and overabused feature. Of course, this turned out to be a large error. One last thing. Sure, string attributes can (and surely would be) used for different purposes in different libraries. The presumption is that this would cause a conflict. But would it? There are two aspects to a UDA - the attribute itself, and the symbol it is attached to. In order to get the UDA for a symbol, one has to look up the symbol. There isn't a global repository of symbols in D. You'd have to say "I want to look in module X for symbols." Why would you look in module X for an attribute that you have no reason to believe applies to symbols from X? How would an attribute for module X's symbols leak out of X on their own? It's not quite analogous to exceptions, because arbitrary exceptions thrown from module X can flow through your code even though you have no idea module X even exists.

This is a valid point, and I think it does not really make sense to only exclude built-in types. Any type not intended for use as an attribute and that is exported to sufficiently many places can have the same behaviour. I'd vote no restrictions at all, or for requiring attribute annotations on the user-defined type and ban user-defined types from being annotations that do not have that.

I'd vote for requiring attribute annotations on the user-defined type and ban user-defined types from being annotations that do not have that. That is really important to allow libs not to step on each other's toes.
Nov 10 2012
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-11-08 00:18, Walter Bright wrote:

 There's another aspect to this.

 D's UDAs are a purely compile time system, attaching arbitrary metadata
 to specific symbols. The other UDA systems I'm aware of appear to be
 runtime systems.

 This implies the use cases will be different - how, I don't really know.
 But I don't know of any other compile time UDA system. Experience with
 runtime systems may not be as applicable.

 Another interesting data point is CTFE. C++11 has CTFE, but it was
 deliberately crippled and burdened with "constexpr". From what I read,
 this was out of fear that it would turn out to be an overused and
 overabused feature. Of course, this turned out to be a large error.

 One last thing. Sure, string attributes can (and surely would be) used
 for different purposes in different libraries. The presumption is that
 this would cause a conflict. But would it? There are two aspects to a
 UDA - the attribute itself, and the symbol it is attached to. In order
 to get the UDA for a symbol, one has to look up the symbol. There isn't
 a global repository of symbols in D. You'd have to say "I want to look
 in module X for symbols." Why would you look in module X for an
 attribute that you have no reason to believe applies to symbols from X?
 How would an attribute for module X's symbols leak out of X on their own?

 It's not quite analogous to exceptions, because arbitrary exceptions
 thrown from module X can flow through your code even though you have no
 idea module X even exists.

I think we should only allow user defined types marked with attribute, i.e. attribute struct foo {} attribute class foo {} attribute interface foo {} attribute enum foo {} And so on. -- /Jacob Carlborg
Nov 08 2012
next sibling parent reply "simendsjo" <simendsjo gmail.com> writes:
On Thursday, 8 November 2012 at 09:05:31 UTC, Jacob Carlborg 
wrote:
 I think we should only allow user defined types marked with 
  attribute, i.e.

  attribute struct foo {}
  attribute class foo {}
  attribute interface foo {}
  attribute enum foo {}

 And so on.

Or struct foo {} interface foo {} enum foo {0 etc
Nov 08 2012
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-11-08 11:56, simendsjo wrote:

 Or
 struct  foo {}
 interface  foo {}
 enum  foo {0
 etc

That syntax looks a bit backwards to me. What if I want to annotate the attribute. serializable struct foo {} Looks a bit confusing which is the name of the attribute and the which is the attached annotation. Vs serializable attribute struct foo {} No confusion here, "foo" is the name of the attribute, the rest is attached annotations. -- /Jacob Carlborg
Nov 08 2012
next sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-11-08 17:26, David Nadlinger wrote:

 Sorry, I could not resist:
 http://cdn.memegenerator.net/instances/400x/29863604.jpg

Hehe, exactly :) -- /Jacob Carlborg
Nov 08 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-11-08 17:53, simendsjo wrote:

 I guess it depends. I find it easier to see that it's an attribute,
 especially when you annotate it. But it's harder to grep for.

 Is foo an attribute or not?
     serializable
     xmlRoot
     attribute
     displayName("foo")
    struct foo {}

 Is foo an attribute or not?
     serializable
     xmlRoot
     displayName("foo")
    struct  foo {}

I don't know really. In that bottom example, the struct declartion almost disappears among all the attributes. -- /Jacob Carlborg
Nov 08 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 08/11/2012 19:55, simendsjo a écrit :
 On Thursday, 8 November 2012 at 17:20:39 UTC, Jacob Carlborg wrote:
 On 2012-11-08 17:53, simendsjo wrote:

 I guess it depends. I find it easier to see that it's an attribute,
 especially when you annotate it. But it's harder to grep for.

 Is foo an attribute or not?
  serializable
  xmlRoot
  attribute
  displayName("foo")
 struct foo {}

 Is foo an attribute or not?
  serializable
  xmlRoot
  displayName("foo")
 struct  foo {}

I don't know really. In that bottom example, the struct declartion almost disappears among all the attributes.

Yeah.. But at least you'll always know where to look. [serializable, xmlRoot, attribute, displayName("foo")] struct foo {} [serializable, xmlRoot, displayName("foo")] struct foo {} but attribute could be required as the last type, and on a line of it's own, giving: [serializable, xmlRoot, displayName("foo")] attribute struct foo {}

More special case isn't a good idea. We already have way too much of them.
Nov 10 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 08/11/2012 11:56, simendsjo a écrit :
 On Thursday, 8 November 2012 at 09:05:31 UTC, Jacob Carlborg wrote:
 I think we should only allow user defined types marked with
  attribute, i.e.

  attribute struct foo {}
  attribute class foo {}
  attribute interface foo {}
  attribute enum foo {}

 And so on.

Or struct foo {} interface foo {} enum foo {0 etc

I love it ! Shorter and achieve the same result, very readable, that is THE solution.
Nov 10 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2012-11-08, 11:56, simendsjo wrote:

 On Thursday, 8 November 2012 at 09:05:31 UTC, Jacob Carlborg wrote:
 I think we should only allow user defined types marked with  attribute,  
 i.e.

  attribute struct foo {}
  attribute class foo {}
  attribute interface foo {}
  attribute enum foo {}

 And so on.

Or struct foo {} interface foo {} enum foo {0 etc

That's actually a very reasonable idea. votes++ -- Simen
Nov 08 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Thursday, 8 November 2012 at 13:19:29 UTC, Jacob Carlborg 
wrote:
 That syntax looks a bit backwards to me. What if I want to 
 annotate the attribute.

Sorry, I could not resist: http://cdn.memegenerator.net/instances/400x/29863604.jpg David
Nov 08 2012
prev sibling next sibling parent "simendsjo" <simendsjo gmail.com> writes:
On Thursday, 8 November 2012 at 13:19:29 UTC, Jacob Carlborg 
wrote:
 On 2012-11-08 11:56, simendsjo wrote:

 Or
 struct  foo {}
 interface  foo {}
 enum  foo {0
 etc

That syntax looks a bit backwards to me. What if I want to annotate the attribute. serializable struct foo {} Looks a bit confusing which is the name of the attribute and the which is the attached annotation. Vs serializable attribute struct foo {} No confusion here, "foo" is the name of the attribute, the rest is attached annotations.

I guess it depends. I find it easier to see that it's an attribute, especially when you annotate it. But it's harder to grep for. Is foo an attribute or not? serializable xmlRoot attribute displayName("foo") struct foo {} Is foo an attribute or not? serializable xmlRoot displayName("foo") struct foo {}
Nov 08 2012
prev sibling next sibling parent "simendsjo" <simendsjo gmail.com> writes:
On Thursday, 8 November 2012 at 17:20:39 UTC, Jacob Carlborg 
wrote:
 On 2012-11-08 17:53, simendsjo wrote:

 I guess it depends. I find it easier to see that it's an 
 attribute,
 especially when you annotate it. But it's harder to grep for.

 Is foo an attribute or not?
    serializable
    xmlRoot
    attribute
    displayName("foo")
   struct foo {}

 Is foo an attribute or not?
    serializable
    xmlRoot
    displayName("foo")
   struct  foo {}

I don't know really. In that bottom example, the struct declartion almost disappears among all the attributes.

Yeah.. But at least you'll always know where to look. [serializable, xmlRoot, attribute, displayName("foo")] struct foo {} [serializable, xmlRoot, displayName("foo")] struct foo {} but attribute could be required as the last type, and on a line of it's own, giving: [serializable, xmlRoot, displayName("foo")] attribute struct foo {}
Nov 08 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Nov 08, 2012 at 10:45:05PM -0500, Nick Sabalausky wrote:
 On Thu, 08 Nov 2012 10:05:30 +0100
 Jacob Carlborg <doob me.com> wrote:
 
 I think we should only allow user defined types marked with
  attribute, i.e.
 
  attribute struct foo {}
  attribute class foo {}
  attribute interface foo {}
  attribute enum foo {}
 
 And so on.
 

I completely agree. I really hate when languages "play it loose" and leave things up to arbitrary convention. It's like duck/structural typing: the *one* thing I hate about D ranges is that they don't force you to explicitly say "Yes, I *intend* this to be an InputRange" (what are we, Go users?). I don't want to see the same unhelpful sloppiness here.

Actually, I just thought of a solution to the whole duck-typing range thing: struct MyRange { // Self-documenting: this struct is intended to be a // range. static assert(isInputRange!MyRange, "Dude, your struct isn't a range!"); // asserts property bool empty() { ... } property auto front() { ... } int popFront() { ... } } struct WhatItShouldBe { // Junior programmer modifies the function signatures // below and the compiler gives him a scolding. static assert(isInputRange!WhatItShouldBe, "Dude, you just broke my range!"); // passes property bool empty() { ... } property auto front() { ... } void popFront() { ... } } void main() { auto needle = "abc"; auto x = find(WhatItShouldBe(), needle); } T -- I see that you JS got Bach.
Nov 08 2012
prev sibling next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Friday, 9 November 2012 at 03:45:11 UTC, Nick Sabalausky wrote:
 the *one* thing I hate about D ranges is that they don't force 
 you to explicitly say "Yes, I *intend* this to be an 
 InputRange" (what are we, Go users?).

Just a note, of course it still wouldn't *force*, but maybe it'd be a good habit to start writing this: struct myrange {...} static assert(isInputRange!myrange); It'd be a simple way to get a check at the point of declaration and to document your intent. Interestingly, we could also do this if the attributes could run through a template: [check!isInputRange] struct myrange{} attribute template check(something, Decl) { static assert(something!Decl); alias check = Decl; } Same thing, diff syntax.
Nov 08 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/8/2012 8:18 PM, Adam D. Ruppe wrote:
 On Friday, 9 November 2012 at 03:45:11 UTC, Nick Sabalausky wrote:
 the *one* thing I hate about D ranges is that they don't force you to
 explicitly say "Yes, I *intend* this to be an InputRange" (what are we, Go
 users?).

Just a note, of course it still wouldn't *force*, but maybe it'd be a good habit to start writing this: struct myrange {...} static assert(isInputRange!myrange); It'd be a simple way to get a check at the point of declaration and to document your intent. Interestingly, we could also do this if the attributes could run through a template: [check!isInputRange] struct myrange{} attribute template check(something, Decl) { static assert(something!Decl); alias check = Decl; }

Many algorithms (at least the ones in Phobos do) already do a check to ensure the inputs are the correct kind of range. I don't think you'll get very far trying to use a range that isn't a range. Of course, you can always still have bugs in your range implementation.
Nov 08 2012
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/8/2012 9:24 PM, Jonathan M Davis wrote:
 So, I really don't see this as a problem.

Neither do I. BTW, there's no compiler magic in the world that will statically guarantee you have a non-buggy implementation of a range.
Nov 08 2012
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 11/8/2012 10:20 PM, H. S. Teoh wrote:
 But of course that's just splitting hairs.

"Let's not go splittin' hares!" -- Bugs Bunny
Nov 08 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-11-09 07:20, H. S. Teoh wrote:

 Well, unittests are a runtime check, and they don't *guarantee*
 anything. (One could, in theory, write a pathological pseudo-range that
 passes basic unittests but fail to behave like a range in some obscure
 corner case. Transient ranges would fall under that category, should we
 decide not to admit them as valid ranges. :-))

 But of course that's just splitting hairs.

But since we do have a language with static typing we can at least do our best to try at catch as many errors as possible at compile time. We don't want to end up as a dynamic language and testing for types in the unit tests. -- /Jacob Carlborg
Nov 08 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-11-09 08:28, Jonathan M Davis wrote:

 But the types are already tested by the templat constraints and the fact that
 they compile at all. It's the functions' runtime behaviors that can't be
 tested, and no language can really test that at compile time, whereas unit
 test _do_ test the runtime behavior. So, you get both static and dynamic
 checks.

Well, I guess you're right. -- /Jacob Carlborg
Nov 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, November 09, 2012 01:44:51 Nick Sabalausky wrote:
 On Thu, 08 Nov 2012 21:10:55 -0800
 
 Walter Bright <newshound2 digitalmars.com> wrote:
 Many algorithms (at least the ones in Phobos do) already do a check
 to ensure the inputs are the correct kind of range. I don't think
 you'll get very far trying to use a range that isn't a range.

It can't check semantics. If something "looks" like a range function, but wasn't written with the explicit intent of actually being one, then it's a crapshoot as to whether the semantics actually conform. But the ducktyping D does do will go and blindly assume.

True, but how likely is it that a type will define all of the necessary range functions and _not_ supposed to be a range? The type must define a specific set of functions, and those functions must compile with at isInputRange at minimum, which checks more than just the function names, and the more complex the range required, the more functions are required and the more specific the tests are (e.g. while isInputRange doesn't test the type of front, isBidirectionalRange does test that front and back have the same type). The odds of accidentally matching isInputRange are already low, but they dwindle to nothing pretty darn quickly as the type of range is more complex, simply because the number of functions and the tests made on them increase to the point that there's pretty much no way that anything will ever accidentally pass them without being intended to be a range. I just don't think that the odds of anything accidentally passing the range traits - even isInputRange - are very high at all. And given that ranges are part of the standard library, I don't think that there's really any excuse for anyone using the names of range functions for something else, not more than one or two of them at once anyway. So, I really think that any worries about this are unfounded. - Jonathan M Davis
Nov 08 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 09/11/2012 07:53, Nick Sabalausky a crit :
 On Thu, 08 Nov 2012 21:24:49 -0800
 Jonathan M Davis<jmdavisProg gmx.com>  wrote:

 On Thursday, November 08, 2012 21:10:55 Walter Bright wrote:
 Many algorithms (at least the ones in Phobos do) already do a check
 to ensure the inputs are the correct kind of range. I don't think
 you'll get very far trying to use a range that isn't a range.

 Of course, you can always still have bugs in your range
 implementation.

Given that a range requires a very specific set of functions, I find it highly unlikely that anything which isn't a range will qualify as one. It's far more likely that you screw up and a range isn't the right kind of range because one of the functions wasn't quite right. There is some danger in a type being incorrectly used with a function when that function requires and tests for only one function, or maybe when it requires two functions. But I would expect that as more is required by a template constraint, it very quickly becomes the case that there's no way that any type would ever pass it with similarly named functions that didn't do the same thing as what they were expected to do. It's just too unlikely that the exact same set of function names would be used for different things, especially as that list grows. And given that ranges are a core part of D's standard library, I don't think that there's much excuse for having a type that has the range functions but isn't supposed to be a range. So, I really don't see this as a problem.

Looking at one set of interfaces in isolation, sure the chances might be low. (Just like the chances of name collisions when hygeine is lacking, and yet we thankfully have a real module system, instead of C's clumsy "Well, it should usually work ok!" garbage.) But it's a terrible precedent. Scale things up, use ducks as common practice, and all of a sudden you're right back into the same old land of "no-hygeine". Bad, sloppy, lazy precedent. AND the presumed benefit of the duckness is minimal at best. Just not a good design, it makes all the wrong tradeoffs.

UDA is a really good way to express that intent.
Nov 10 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Nov 08, 2012 at 11:51:29PM -0500, Nick Sabalausky wrote:
 On Thu, 8 Nov 2012 20:17:24 -0800
 "H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 
 Actually, I just thought of a solution to the whole duck-typing range
 thing:
 
 	struct MyRange {
 		// Self-documenting: this struct is intended to be a
 		// range.
 		static assert(isInputRange!MyRange,
 			"Dude, your struct isn't a range!"); //
 asserts
 
 

"Adam D. Ruppe" <destructionator gmail.com> wrote:
 
 Just a note, of course it still wouldn't *force*, but maybe it'd 
 be a good habit to start writing this:
 
 struct myrange {...}
 static assert(isInputRange!myrange);
 

Those are only half-solutions as they only prevent false-negatives, not false-positives. Plus, there's nothing to prevent people from forgetting to do it in the first place.

IOW, you want the user-defined type to declare that it's an input range, and not just some random struct that happens to have input range like functions? What about modifying isInputRange to be something like this: template isInputRange(R) { static if (R.implementsInputRange && /* ... check for input range properties here */) { enum isInputRange = true; } else { enum isInputRange = false; } } Then all input ranges will have to explicitly declare they are an input range thus: struct MyInpRange { // This asserts that we're trying to be an input range enum implementsInputRange = true; // ... define .empty, .front, .popFront here } Any prospective input range that doesn't define implementsInputRange will be rejected by all input range functions. (Of course, that's just a temporary name, you can probably think of a better one.) You can also make it a mixin, or something like that, if you want to avoid the tedium of defining an enum to be true every single time. T -- Long, long ago, the ancient Chinese invented a device that lets them see through walls. It was called the "window".
Nov 08 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 11/9/12, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 Then all input ranges will have to explicitly declare they are an input
 range thus:

Or: Change definition of isInputRange to: --- template isInputRange(T) { enum bool isInputRange = (__attributes(T, RequiresInputRangeCheck) && __attributes(T, IsInputRangeAttr)) || is(typeof( (inout int _dummy=0) { R r = void; // can define a range object if (r.empty) {} // can test for empty r.popFront(); // can invoke popFront() auto h = r.front; // can get the front of the range })); } --- and in your user module: --- module foo; RequiresInputRangeCheck: // apply attribute to all declarations in this module IsInputRangeAttr struct MyRange { /* front/popFront/empty */ } struct NotARange { /* front/popFront/empty defined but not designed to be a range */ } --- --- static assert(isInputRange!MyRange); static assert(!(isInputRange!NotARange)); --- That way you keep compatibility with existing ranges and introduce extra safety check for new types which want to be checked.
Nov 08 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Nov 08, 2012 at 09:37:00PM -0800, H. S. Teoh wrote:
 On Thu, Nov 08, 2012 at 11:51:29PM -0500, Nick Sabalausky wrote:

 Those are only half-solutions as they only prevent false-negatives,
 not false-positives. Plus, there's nothing to prevent people from
 forgetting to do it in the first place.

IOW, you want the user-defined type to declare that it's an input range, and not just some random struct that happens to have input range like functions? What about modifying isInputRange to be something like this: template isInputRange(R) { static if (R.implementsInputRange && /* ... check for input range properties here */) { enum isInputRange = true; } else { enum isInputRange = false; } } Then all input ranges will have to explicitly declare they are an input range thus: struct MyInpRange { // This asserts that we're trying to be an input range enum implementsInputRange = true; // ... define .empty, .front, .popFront here } Any prospective input range that doesn't define implementsInputRange will be rejected by all input range functions. (Of course, that's just a temporary name, you can probably think of a better one.) You can also make it a mixin, or something like that, if you want to avoid the tedium of defining an enum to be true every single time.

Here's a slight refinement: // Note: untested code mixin template imAnInputRange() { static assert(isInputRange!(typeof(this))); enum implementsInputRange = true; } struct MyInpRange { // This should croak loudly if this struct isn't a valid // input range. Omitting this line makes range functions // reject it too (provided we modify isInputRange as // described above). mixin imAnInputRange; // implement range functions here } T -- It only takes one twig to burn down a forest.
Nov 08 2012
prev sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
On Friday, 9 November 2012 at 05:11:05 UTC, Walter Bright wrote:
 Many algorithms (at least the ones in Phobos do) already do a 
 check to ensure the inputs are the correct kind of range.

There's two annoyances there: 1) the error messages can be godawful, not giving you any indication why. It's like "Foo doesn't match any template" and you've gotta figure out just what is the problem. 2) The error is at the call site, not the declaration site. Did you just flip the order of parameters? Or is the struct itself bad?
Nov 09 2012
prev sibling next sibling parent "Nathan M. Swan" <nathanmswan gmail.com> writes:
On Wednesday, 7 November 2012 at 23:18:41 UTC, Walter Bright 
wrote:
 Started a new thread on this.

 On 11/7/2012 3:05 AM, Leandro Lucarella wrote:
 OK, that's another thing. And maybe a reason for listening to

 more experience with UDAs than you.

 For me the analogy with Exceptions is pretty good. The issues

 of throwing anything or annotating a symbol with anything

 type are pretty much the same. I only see functions making

 as annotations too (that's what Python do with annotations,

 is the same as symbol = annotation(symbol), but is quite a

There's another aspect to this. D's UDAs are a purely compile time system, attaching arbitrary metadata to specific symbols. The other UDA systems I'm aware of appear to be runtime systems. This implies the use cases will be different - how, I don't really know. But I don't know of any other compile time UDA system. Experience with runtime systems may not be as applicable. Another interesting data point is CTFE. C++11 has CTFE, but it was deliberately crippled and burdened with "constexpr". From what I read, this was out of fear that it would turn out to be an overused and overabused feature. Of course, this turned out to be a large error. One last thing. Sure, string attributes can (and surely would be) used for different purposes in different libraries. The presumption is that this would cause a conflict. But would it? There are two aspects to a UDA - the attribute itself, and the symbol it is attached to. In order to get the UDA for a symbol, one has to look up the symbol. There isn't a global repository of symbols in D. You'd have to say "I want to look in module X for symbols." Why would you look in module X for an attribute that you have no reason to believe applies to symbols from X? How would an attribute for module X's symbols leak out of X on their own? It's not quite analogous to exceptions, because arbitrary exceptions thrown from module X can flow through your code even though you have no idea module X even exists.

In module sql.d: /// For every field marked ["serialize"], add to table void saveToDatabase(T)(DBConnection db, T model); In module json.d: /// For every field marked ["serialize"], add to JSON object string jsonSerialize(T)(T obj); In module userinfo.d: ["dbmodel"] struct UserModel { ["serialize"] string username; // What do you do if you want this in the database, but not the JSON? string password; ["serialize"] Content ownedContentOrWhateverThisWebsiteIs; } The only solution to this question is to differentiate "db_serialize" and "json_serialize"; looks a lot like C, doesn't it? My suggested soluion: annotation (with [] UDA syntax): module sql; annotation enum model; annotation enum serialize; module json; annotation enum serialize; module userinfo; import sql, json; [sql.model] struct UserModel { [sql.serialize, json.serialize] string username; [sql.serialize] string password; [sql.serialize, json.serialize] Content content; } My thoughts, NMS
Nov 08 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Thu, 08 Nov 2012 10:05:30 +0100
Jacob Carlborg <doob me.com> wrote:
 
 I think we should only allow user defined types marked with
  attribute, i.e.
 
  attribute struct foo {}
  attribute class foo {}
  attribute interface foo {}
  attribute enum foo {}
 
 And so on.
 

I completely agree. I really hate when languages "play it loose" and leave things up to arbitrary convention. It's like duck/structural typing: the *one* thing I hate about D ranges is that they don't force you to explicitly say "Yes, I *intend* this to be an InputRange" (what are we, Go users?). I don't want to see the same unhelpful sloppiness here.
Nov 08 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Thu, 8 Nov 2012 20:17:24 -0800
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 
 Actually, I just thought of a solution to the whole duck-typing range
 thing:
 
 	struct MyRange {
 		// Self-documenting: this struct is intended to be a
 		// range.
 		static assert(isInputRange!MyRange,
 			"Dude, your struct isn't a range!"); //
 asserts
 
 

"Adam D. Ruppe" <destructionator gmail.com> wrote:
 
 Just a note, of course it still wouldn't *force*, but maybe it'd 
 be a good habit to start writing this:
 
 struct myrange {...}
 static assert(isInputRange!myrange);
 

Those are only half-solutions as they only prevent false-negatives, not false-positives. Plus, there's nothing to prevent people from forgetting to do it in the first place. I outlined and implemented a proof-of-concept for a full solution middle of last year: http://www.semitwist.com/articles/EfficientAndFlexible/MultiplePages/Page5/ The basic gist (and there's definitely still room for plenty of improvement): // The General Tool: string declareInterface(string interfaceName, string thisType) { return ` // Announce what interface this implements. static enum _this_implements_interface_`~interfaceName~`_ = true; // Verify this actually does implement the interface static assert( is`~interfaceName~`!(`~thisType~`), "This type fails to implement `~interfaceName~`" ); `; } // Sample usage: template isFoo(T) { immutable bool isFoo = __traits(compiles, (){ T t; static assert(T._this_implements_interface_Foo_); t.fooNum = 5; int x = t.fooFunc(""); // Check everything else here }); } struct MyFoo { // Can probably be more DRY with fancy trickery mixin(declareInterface("Foo", "MyFoo")); int fooNum; int fooFunc(string a) {...} } What I'd really like to see is a way to implement declareInterface so that the isFoo is replaced by an ordinary "interface Foo {...}", which MyFoo's members are automatically checked against. I suspect that should be possible with some fancy metaprogramming-fu.
Nov 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, November 08, 2012 21:10:55 Walter Bright wrote:
 Many algorithms (at least the ones in Phobos do) already do a check to
 ensure the inputs are the correct kind of range. I don't think you'll get
 very far trying to use a range that isn't a range.
 
 Of course, you can always still have bugs in your range implementation.

Given that a range requires a very specific set of functions, I find it highly unlikely that anything which isn't a range will qualify as one. It's far more likely that you screw up and a range isn't the right kind of range because one of the functions wasn't quite right. There is some danger in a type being incorrectly used with a function when that function requires and tests for only one function, or maybe when it requires two functions. But I would expect that as more is required by a template constraint, it very quickly becomes the case that there's no way that any type would ever pass it with similarly named functions that didn't do the same thing as what they were expected to do. It's just too unlikely that the exact same set of function names would be used for different things, especially as that list grows. And given that ranges are a core part of D's standard library, I don't think that there's much excuse for having a type that has the range functions but isn't supposed to be a range. So, I really don't see this as a problem. - Jonathan M Davis
Nov 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, November 08, 2012 21:49:52 Walter Bright wrote:
 BTW, there's no compiler magic in the world that will statically guarantee
 you have a non-buggy implementation of a range.

That's what unit tests are for. :) - Jonathan M Davis
Nov 08 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Nov 08, 2012 at 10:03:03PM -0800, Jonathan M Davis wrote:
 On Thursday, November 08, 2012 21:49:52 Walter Bright wrote:
 BTW, there's no compiler magic in the world that will statically
 guarantee you have a non-buggy implementation of a range.


Yeah, that's one major missing feature from D/Phobos/etc.: a mixin template called EliminateBugs that will fix all your program's bugs for you. I think that should be the next top priority on D's to-do list! ;-)
 That's what unit tests are for. :)

Well, unittests are a runtime check, and they don't *guarantee* anything. (One could, in theory, write a pathological pseudo-range that passes basic unittests but fail to behave like a range in some obscure corner case. Transient ranges would fall under that category, should we decide not to admit them as valid ranges. :-)) But of course that's just splitting hairs. T -- Amateurs built the Ark; professionals built the Titanic.
Nov 08 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Thu, 8 Nov 2012 21:37:00 -0800
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 
 IOW, you want the user-defined type to declare that it's an input
 range, and not just some random struct that happens to have input
 range like functions?
 

Yes.
 What about modifying isInputRange to be something like this:
 
 	template isInputRange(R) {
 		static if (R.implementsInputRange &&
 			/* ... check for input range properties here
 */) {
 			enum isInputRange = true;
 		} else {
 			enum isInputRange = false;
 		}
 	}
 
 Then all input ranges will have to explicitly declare they are an
 input range thus:
 
 	struct MyInpRange {
 		// This asserts that we're trying to be an input range
 		enum implementsInputRange = true;
 
 		// ... define .empty, .front, .popFront here
 	}
 

Close. At that point you need two steps: struct MyInpRange { // Declare this as an input range enum implementsInputRange = true; // Enforce this really *IS* as an input range static assert(isInputRange!MyRange, "Dude, your struct isn't a range!"); // asserts // ... define .empty, .front, .popFront here } My suggestion was to take basically that, and then wrap up the "Declare and Enfore" in one simple step: struct MyInpRange { // Generate & mixin *both* the "enum" and the "static assert" mixin(implements!InputRange); // ... define .empty, .front, .popFront here } /Dreaming: Of course, it'd be even nicer still to have all this wrapped up in some language sugar (D3? ;) ) and just do something like: struct interface InputRange { // ... *declare* .empty, .front, .popFront here } struct interface ForwardRange : InputRange { // ... *declare* .save here } struct MyForwardRange : ForwardRange { // ... define .empty, .front, .popFront, .save here // Actually validated by the compiler } Which would then amount to what we're doing by hand up above. So kinda like Go, except not error-prone and ducky and all shitty.
Nov 08 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Thu, 08 Nov 2012 21:10:55 -0800
Walter Bright <newshound2 digitalmars.com> wrote:
 
 Many algorithms (at least the ones in Phobos do) already do a check
 to ensure the inputs are the correct kind of range. I don't think
 you'll get very far trying to use a range that isn't a range.
 

It can't check semantics. If something "looks" like a range function, but wasn't written with the explicit intent of actually being one, then it's a crapshoot as to whether the semantics actually conform. But the ducktyping D does do will go and blindly assume.
Nov 08 2012
prev sibling next sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Thu, 08 Nov 2012 21:24:49 -0800
Jonathan M Davis <jmdavisProg gmx.com> wrote:

 On Thursday, November 08, 2012 21:10:55 Walter Bright wrote:
 Many algorithms (at least the ones in Phobos do) already do a check
 to ensure the inputs are the correct kind of range. I don't think
 you'll get very far trying to use a range that isn't a range.
 
 Of course, you can always still have bugs in your range
 implementation.

Given that a range requires a very specific set of functions, I find it highly unlikely that anything which isn't a range will qualify as one. It's far more likely that you screw up and a range isn't the right kind of range because one of the functions wasn't quite right. There is some danger in a type being incorrectly used with a function when that function requires and tests for only one function, or maybe when it requires two functions. But I would expect that as more is required by a template constraint, it very quickly becomes the case that there's no way that any type would ever pass it with similarly named functions that didn't do the same thing as what they were expected to do. It's just too unlikely that the exact same set of function names would be used for different things, especially as that list grows. And given that ranges are a core part of D's standard library, I don't think that there's much excuse for having a type that has the range functions but isn't supposed to be a range. So, I really don't see this as a problem.

Looking at one set of interfaces in isolation, sure the chances might be low. (Just like the chances of name collisions when hygeine is lacking, and yet we thankfully have a real module system, instead of C's clumsy "Well, it should usually work ok!" garbage.) But it's a terrible precedent. Scale things up, use ducks as common practice, and all of a sudden you're right back into the same old land of "no-hygeine". Bad, sloppy, lazy precedent. AND the presumed benefit of the duckness is minimal at best. Just not a good design, it makes all the wrong tradeoffs.
Nov 08 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 11/9/12, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 Yeah, that's one major missing feature from D/Phobos/etc.: a mixin
 template called EliminateBugs that will fix all your program's bugs for
 you. I think that should be the next top priority on D's to-do list! ;-)

Considering you can do a string import of the module you're in (provided the -J switch), and the fact that Pegged works at compile-time, that wouldn't be a far-fetched dream at all. :p
Nov 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, November 09, 2012 01:53:40 Nick Sabalausky wrote:
 Looking at one set of interfaces in isolation, sure the chances might
 be low. (Just like the chances of name collisions when hygeine is
 lacking, and yet we thankfully have a real module system, instead of C's
 clumsy "Well, it should usually work ok!" garbage.) But it's a terrible
 precedent. Scale things up, use ducks as common practice, and all of a
 sudden you're right back into the same old land of "no-hygeine". Bad,
 sloppy, lazy precedent. AND the presumed benefit of the duckness is
 minimal at best. Just not a good design, it makes all the wrong
 tradeoffs.

As long as your template constraints requires more than a couple of functions and actually tests much of anything about what those functions return or what arguments can be passed to them, I find it very unlikely that anything will accidentally pass them. Too many coincidences would be required to end up with a set of functions that matched when they weren't supposed to . It's only likely to be problem if you're checking only one or two functions and don't check much beyond their existence. It just doesn't take very many checks before it's highly unlikely for anyone to have created a type with functions with the same names and whose signatures are close enough to pass a template constraint when they're not supposed to. - Jonathan M Davis
Nov 08 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, November 09, 2012 08:21:38 Jacob Carlborg wrote:
 On 2012-11-09 07:20, H. S. Teoh wrote:
 Well, unittests are a runtime check, and they don't *guarantee*
 anything. (One could, in theory, write a pathological pseudo-range that
 passes basic unittests but fail to behave like a range in some obscure
 corner case. Transient ranges would fall under that category, should we
 decide not to admit them as valid ranges. :-))
 
 But of course that's just splitting hairs.

But since we do have a language with static typing we can at least do our best to try at catch as many errors as possible at compile time. We don't want to end up as a dynamic language and testing for types in the unit tests.

But the types are already tested by the templat constraints and the fact that they compile at all. It's the functions' runtime behaviors that can't be tested, and no language can really test that at compile time, whereas unit test _do_ test the runtime behavior. So, you get both static and dynamic checks. - Jonathan M Davis
Nov 08 2012
prev sibling next sibling parent "Jonas Drewsen" <nospam4321 hotmail.com> writes:
On Friday, 9 November 2012 at 07:29:28 UTC, Jonathan M Davis 
wrote:
 On Friday, November 09, 2012 08:21:38 Jacob Carlborg wrote:
 On 2012-11-09 07:20, H. S. Teoh wrote:
 Well, unittests are a runtime check, and they don't 
 *guarantee*
 anything. (One could, in theory, write a pathological 
 pseudo-range that
 passes basic unittests but fail to behave like a range in 
 some obscure
 corner case. Transient ranges would fall under that 
 category, should we
 decide not to admit them as valid ranges. :-))
 
 But of course that's just splitting hairs.

But since we do have a language with static typing we can at least do our best to try at catch as many errors as possible at compile time. We don't want to end up as a dynamic language and testing for types in the unit tests.

But the types are already tested by the templat constraints and the fact that they compile at all. It's the functions' runtime behaviors that can't be tested, and no language can really test that at compile time, whereas unit test _do_ test the runtime behavior. So, you get both static and dynamic checks.

I guess some runtime behavior could actually be tested at compile time using CTFE? int doubleMe(int a) { return 2 * a } static test = doubleMe(3); static assert(test == 6); But maybe that is splittin' hares as well :) /Jonas
Nov 09 2012
prev sibling next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
On Friday, 9 November 2012 at 05:25:12 UTC, Jonathan M Davis 
wrote:
 Given that a range requires a very specific set of functions, I 
 find it highly unlikely that anything which isn't a range will
 qualify as one.

I've had it happen to me several times. Let me show you some code from my dom.d: mixin template JavascriptStyleDispatch() { string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want. class Element { // name != "popFront" is so duck typing doesn't think it's a range property string opDispatch(string name)(string v = null) if(name != "popFront") { } struct CssRule { string opDispatch(string nameGiven)(string value = null) if(nameGiven != "popFront") { } From prototype.d: class PrototypeObject { // the constraint is needed otherwise the Variant constructor fails property ref PrototypeObject opDispatch(string name)() if(name != "length") { } (I'm not sure if this last one is a range problem, because it was Variant that was confused, but the dom ones definitely were). Of course, they all have something in common: an open opDispatch. But that's a valid D feature so surely I'm not the only one using it. Every one of those constraints come from it actually being a problem at one point or another.
Nov 09 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, November 09, 2012 14:14:01 Adam D. Ruppe wrote:
 Of course, they all have something in common: an open opDispatch.
 But that's a valid D feature so surely I'm not the only one using
 it.

Okay. I can see it happening there, and alias this could cause similar problems, but if you're actually naming the functions yourself, I find it very hard to believe that it's every a problem. opDispatch and alias this do pose their own sorts of problems though (alias this in particular can do funny things to make template constraints pass but still make it so that the template doesn't work). - Jonathan M Davis
Nov 09 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 11/09/12 07:41, Nick Sabalausky wrote:
 Of course, it'd be even nicer still to have all this wrapped up in
 some language sugar (D3? ;) ) and just do something like:
 
 struct interface InputRange {
     // ... *declare* .empty, .front, .popFront here
 }
 
 struct interface ForwardRange : InputRange {
     // ... *declare* .save here
 }
 
 struct MyForwardRange : ForwardRange {
     // ... define .empty, .front, .popFront, .save here
     // Actually validated by the compiler
 }
 
 Which would then amount to what we're doing by hand up above. So kinda
 like Go, except not error-prone and ducky and all shitty.

This would actually be backwards compatible and also relatively forward compatible - does not need to wait for a D3. However, while it's a step in the right direction, doing it like that would be to limiting. Compare with: interface template InputRange(T) { enum InputRange = __traits(compiles, {T r; /* check the i/f here */}); } struct MyInputRange : InputRange { enum empty = false; enum front = 42; void popFront() {} } Ie requiring a certain interface to be present is fine, requiring a certain set of function signatures is too restrictive (and could still be done inside the interface-template when necessary). I used "interface-templates" because this feature should not clash with /normal/ struct inheritance, once that one emerges. All the boilerplate in the InputRange template above could be made implicit and it could then look like this: interface template InputRange(T) { T r; bool e = r.empty; r.popFront(); auto f = r.front; } else throw("Not an input range"); // "static throw", "assert", whatever. These i/f templates would also be useful in places where the current 'isInputRange' hack is used; would significantly improve both readability and error reporting (that's the reason for the else clause). artur
Nov 10 2012