digitalmars.D - UDAs - Restrict to User Defined Types?
- Walter Bright (24/31) Nov 07 2012 There's another aspect to this.
- Adam D. Ruppe (9/9) Nov 07 2012 I think restricting to user types makes sense because the type is
- Tobias Pankrath (4/7) Nov 07 2012 What if you are using two different libraries in X that require
- David Nadlinger (15/26) Nov 07 2012 Wasn't the @every_body_writes_their_names_like_this in C earlier
- Simen Kjaeraas (7/46) Nov 07 2012 I'm not sure what the correct answer is here, but I think the solutions
- Timon Gehr (8/47) Nov 07 2012 This is a valid point, and I think it does not really make sense to only...
- deadalnix (4/58) Nov 10 2012 I'd vote for requiring @attribute annotations on the user-defined type
- Jacob Carlborg (9/32) Nov 08 2012 I think we should only allow user defined types marked with @attribute, ...
- simendsjo (7/14) Nov 08 2012 Or
- Simen Kjaeraas (4/19) Nov 08 2012 That's actually a very reasonable idea. votes++
- Jacob Carlborg (12/17) Nov 08 2012 That syntax looks a bit backwards to me. What if I want to annotate the
- David Nadlinger (5/7) Nov 08 2012 Sorry, I could not resist:
- Jacob Carlborg (4/6) Nov 08 2012 Hehe, exactly :)
- simendsjo (16/31) Nov 08 2012 I guess it depends. I find it easier to see that it's an
- Jacob Carlborg (5/18) Nov 08 2012 I don't know really. In that bottom example, the struct declartion
- deadalnix (3/18) Nov 10 2012 I love it ! Shorter and achieve the same result, very readable, that is
- Nick Sabalausky (8/19) Nov 08 2012 I completely agree. I really hate when languages "play it loose" and
- H. S. Teoh (28/48) Nov 08 2012 Actually, I just thought of a solution to the whole duck-typing range
- Nick Sabalausky (49/68) Nov 08 2012 On Fri, 09 Nov 2012 05:18:59 +0100
- H. S. Teoh (30/57) Nov 08 2012 IOW, you want the user-defined type to declare that it's an input range,
- Nick Sabalausky (34/61) Nov 08 2012 Yes.
- Artur Skawina (34/52) Nov 10 2012 This would actually be backwards compatible and also relatively
- Andrej Mitrovic (32/34) Nov 08 2012 Or:
- H. S. Teoh (20/57) Nov 08 2012 [...]
- Adam D. Ruppe (15/18) Nov 08 2012 Just a note, of course it still wouldn't *force*, but maybe it'd
- Walter Bright (5/22) Nov 08 2012 Many algorithms (at least the ones in Phobos do) already do a check to e...
- Jonathan M Davis (17/22) Nov 08 2012 Given that a range requires a very specific set of functions, I find it ...
- Walter Bright (4/5) Nov 08 2012 Neither do I.
- Jonathan M Davis (3/5) Nov 08 2012 That's what unit tests are for. :)
- H. S. Teoh (14/18) Nov 08 2012 Yeah, that's one major missing feature from D/Phobos/etc.: a mixin
- Walter Bright (3/4) Nov 08 2012 "Let's not go splittin' hares!"
- Jacob Carlborg (7/13) Nov 08 2012 But since we do have a language with static typing we can at least do
- Jonathan M Davis (7/20) Nov 08 2012 But the types are already tested by the templat constraints and the fact...
- Jacob Carlborg (4/9) Nov 08 2012 Well, I guess you're right.
- Jonas Drewsen (9/39) Nov 09 2012 I guess some runtime behavior could actually be tested at compile
- Andrej Mitrovic (4/7) Nov 08 2012 Considering you can do a string import of the module you're in
- Nick Sabalausky (11/37) Nov 08 2012 Looking at one set of interfaces in isolation, sure the chances might
- Jonathan M Davis (12/21) Nov 08 2012 As long as your template constraints requires more than a couple of func...
- deadalnix (2/39) Nov 10 2012 UDA is a really good way to express that intent.
- Adam D. Ruppe (32/35) Nov 09 2012 I've had it happen to me several times. Let me show you some code
- Jonathan M Davis (8/11) Nov 09 2012 Okay. I can see it happening there, and alias this could cause similar
- Nick Sabalausky (6/11) Nov 08 2012 It can't check semantics. If something "looks" like a range function,
- Jonathan M Davis (20/31) Nov 08 2012 True, but how likely is it that a type will define all of the necessary ...
- Adam D. Ruppe (8/10) Nov 09 2012 There's two annoyances there:
- Nathan M. Swan (36/79) Nov 08 2012 In module sql.d:
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
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
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
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
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
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
Le 08/11/2012 01:11, Timon Gehr a écrit :On 11/08/2012 12:18 AM, Walter Bright wrote: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.Started a new thread on this. On 11/7/2012 3:05 AM, Leandro Lucarella wrote: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.OK, that's another thing. And maybe a reason for listening to peoplehavingmore experience with UDAs than you. For me the analogy with Exceptions is pretty good. The issues anconveniencesof throwing anything or annotating a symbol with anything instead ofjusttype are pretty much the same. I only see functions making sense tobe acceptedas annotations too (that's what Python do with annotations,annotation symbolis the same as symbol = annotation(symbol), but is quite a differentlanguage). 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 10 2012
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
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
On 2012-11-08, 11:56, simendsjo wrote:On Thursday, 8 November 2012 at 09:05:31 UTC, Jacob Carlborg wrote:That's actually a very reasonable idea. votes++ -- SimenI 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
On 2012-11-08 11:56, simendsjo wrote:Or struct foo {} interface foo {} enum foo {0 etcThat 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
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
On 2012-11-08 17:26, David Nadlinger wrote:Sorry, I could not resist: http://cdn.memegenerator.net/instances/400x/29863604.jpgHehe, exactly :) -- /Jacob Carlborg
Nov 08 2012
On Thursday, 8 November 2012 at 13:19:29 UTC, Jacob Carlborg wrote:On 2012-11-08 11:56, 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 {}Or struct foo {} interface foo {} enum foo {0 etcThat 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.
Nov 08 2012
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
On Thursday, 8 November 2012 at 17:20:39 UTC, Jacob Carlborg wrote:On 2012-11-08 17:53, simendsjo wrote: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 {}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.
Nov 08 2012
Le 08/11/2012 19:55, simendsjo a écrit :On Thursday, 8 November 2012 at 17:20:39 UTC, Jacob Carlborg wrote:More special case isn't a good idea. We already have way too much of them.On 2012-11-08 17:53, simendsjo wrote: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 {}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.
Nov 10 2012
Le 08/11/2012 11:56, simendsjo a écrit :On Thursday, 8 November 2012 at 09:05:31 UTC, Jacob Carlborg wrote:I love it ! Shorter and achieve the same result, very readable, that is THE solution.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 10 2012
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
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: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.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
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!"); // assertsOn Fri, 09 Nov 2012 05:18:59 +0100 "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
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: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".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!"); // assertsOn Fri, 09 Nov 2012 05:18:59 +0100 "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.
Nov 08 2012
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
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
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
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:[...][...] 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.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.
Nov 08 2012
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
On 11/8/2012 8:18 PM, Adam D. Ruppe wrote:On Friday, 9 November 2012 at 03:45:11 UTC, Nick Sabalausky 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.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; }
Nov 08 2012
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
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
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
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: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! ;-)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. :)[...] 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
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
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
On Friday, November 09, 2012 08:21:38 Jacob Carlborg wrote:On 2012-11-09 07:20, H. S. Teoh 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. - Jonathan M DavisWell, 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.
Nov 08 2012
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
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: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 :) /JonasOn 2012-11-09 07:20, H. S. Teoh 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, 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.
Nov 09 2012
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
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: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.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.
Nov 08 2012
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
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:UDA is a really good way to express that intent.On Thursday, November 08, 2012 21:10:55 Walter Bright 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.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.
Nov 10 2012
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
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
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
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: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 DavisMany 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
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
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: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, NMSOK, that's another thing. And maybe a reason for listening topeople havingmore experience with UDAs than you. For me the analogy with Exceptions is pretty good. The issuesan conveniencesof throwing anything or annotating a symbol with anythinginstead of justtype are pretty much the same. I only see functions makingsense to be acceptedas annotations too (that's what Python do with annotations,annotation symbolis the same as symbol = annotation(symbol), but is quite adifferent 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 08 2012