digitalmars.D - Interfaces, traits, concepts, and my idea for a DIP
- Atila Neves (62/62) Jul 28 2015 So I missed the boat on the lengthy Rust traits discussion on the
- Jack Stouffer (6/7) Jul 28 2015 I don't think you even need new syntax to get the error messages.
- Atila Neves (4/11) Jul 28 2015 I guess, but not easily. I've written template mixins to do that
- Kagamin (2/4) Jul 28 2015 What was awkward?
- Atila Neves (4/8) Jul 29 2015 Writing a generic solution that would work for multiple
- Daniel =?UTF-8?B?S296w6Fr?= (10/94) Jul 28 2015 I was thinking about same many times before. With one difference:
- jmh530 (24/29) Jul 28 2015 I get the change from isInputRange to InputRange, because
- Tofu Ninja (24/33) Jul 28 2015 I have actually thought about this as well, and a thing that
- Sean Campbell (6/13) Jul 30 2015 +1
- ChangLong (4/10) Jul 28 2015 +1
- Roland Hadinger (9/15) Jul 29 2015 What about this instead:
- Atila Neves (5/22) Jul 29 2015 That looks nice, but who's going to check it? UDAs have to be
- Tofu Ninja (6/35) Jul 29 2015 If you write:
- Tofu Ninja (18/23) Jul 29 2015 A UDA can reference the thing it's being attached to with no
- Atila Neves (30/55) Jul 30 2015 You still wouldn't get a better error message here than with:
- jmh530 (23/53) Jul 30 2015 I might be overthinking this.
- Atila Neves (6/30) Jul 31 2015 Check the out the PR I mentioned:
- Biotronic (12/32) Jul 31 2015 Why are there two different things in the first place?
- cym13 (2/13) Jul 31 2015 That would be better !
- Atila Neves (15/56) Jul 31 2015 Because you want to:
- Atila Neves (8/42) Jul 31 2015 Notice however, that (again, in the PR), the "real" definition
- jmh530 (35/42) Jul 31 2015 Looking at the PR also resolved my earlier question. Running the
- Piotrek (6/41) Aug 01 2015 Nice. Here are the actual error messages for the record:
- Sebastiaan Koppe (3/23) Jul 31 2015 He does have a point. isInputRange!Bar = (checkInputRange!Bar ==
- cym13 (34/95) Jul 31 2015 The one thing I'm dissatisfied with this proposition is
- Andrei Alexandrescu (3/4) Jul 29 2015 This is straight from the "creative use of what we have" mantra that I
- Atila Neves (6/11) Jul 29 2015 I went back to my copy of Adam's book and made a PR based on it:
So I missed the boat on the lengthy Rust traits discussion on the other thread. I confess I didn't have time to read all of it so forgive me if I bring up something that's already been said there. Since last year, after having written a ton of generic code with compile-time reflection and (although it didn't have a name at the time) design by introspection, I got pretty annoyed at having to debug why my template constraints failed. So I had an idea and it's been waiting for me to write a DIP ever since. I don't think I have time right now to write the DIP properly, but given the recent discussion, here's a sketch. Say I have: void func(R)(R range) if(isInputRange!R) { ... } Maybe I didn't even write the function, maybe it's in Phobos. But I'm interested in implementing an input range but this fails to instantiate. I have no idea why. Now, it's usually good practice to include a `static assert(isInputRange!MyType)` somewhere, but if that fails... you have no idea why. Was it `popFront`? `front`? `empty`? Why didn't it compile? I've resorted to copying the code from the lambda in the customary `is(typeof({...}))` in the template constraint and forcing the compiler to give the message that it's hiding from me. The compiler knows why the static assert failed, but it won't tell me unless I force it to. This is analogous to dynamic dispatch except, in that case, the compiler will quite gladly tell you if you forgot to implement a virtual function and much more besides. And why can it do that? Because you told it your class implements an interface or inherits from a class with abstract functions / methods. So... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... } or struct MyRange: static isInputRange { ... } // that way classes could do this too Then instead of: foo.d(9): Error: static assert (isInputRange!(MyRange)) is false I'd get: foo.d(9): Error: MyRange does not implement popFront I'd hoped to paste the result of trying to compile isInputRange in the 2nd example but apparently right now I get the very horrible: foo.d(17): Error: template std.range.primitives.popFront cannot deduce function from argument types !()(MyRange), candidates are: /usr/include/dlang/dmd/std/range/primitives.d(1992): std.range.primitives.popFront(T)(ref T[] a) if (!isNarrowString!(T[]) && !is(T[] == void[])) /usr/include/dlang/dmd/std/range/primitives.d(2015): std.range.primitives.popFront(C)(ref C[] str) if (isNarrowString!(C[])) foo.d(22): Error: template instance foo.func!(MyRange) error instantiating At least it mentions `popFront`. Anyway, you understand what I want from this. The only I think I've seen that comes close to achieving this is a massive hack abusing __ctfe in Adam's book. I've lost count of how many times a template constraint failed because of some stupid thing like safe functions not being able to call system ones and it was really hard figuring out why. I don't want C++ concepts. I just want compiler error messages pointing me in the general direction of why my type fails a template constraint. This is especially bad when there are several overloads with different constraints, I usually have to comment out code until the offending one presents itself. Atila
Jul 28 2015
Overall a great idea! On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:struct MyRange: static isInputRange { ... }I don't think you even need new syntax to get the error messages. If you have a static assert in your unit tests for isInputRange!MyType, then theoretically you could just modify isInputRange to return error messages, right?
Jul 28 2015
On Tuesday, 28 July 2015 at 13:02:06 UTC, Jack Stouffer wrote:Overall a great idea! On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:I guess, but not easily. I've written template mixins to do that before and it was awkward. Atilastruct MyRange: static isInputRange { ... }I don't think you even need new syntax to get the error messages. If you have a static assert in your unit tests for isInputRange!MyType, then theoretically you could just modify isInputRange to return error messages, right?
Jul 28 2015
On Tuesday, 28 July 2015 at 13:10:43 UTC, Atila Neves wrote:I guess, but not easily. I've written template mixins to do that before and it was awkward.What was awkward?
Jul 28 2015
On Wednesday, 29 July 2015 at 06:05:37 UTC, Kagamin wrote:On Tuesday, 28 July 2015 at 13:10:43 UTC, Atila Neves wrote:Writing a generic solution that would work for multiple constraints without code repetition. AtilaI guess, but not easily. I've written template mixins to do that before and it was awkward.What was awkward?
Jul 29 2015
On Tue, 28 Jul 2015 12:49:15 +0000 "Atila Neves" <atila.neves gmail.com> wrote:So I missed the boat on the lengthy Rust traits discussion on the other thread. I confess I didn't have time to read all of it so forgive me if I bring up something that's already been said there. Since last year, after having written a ton of generic code with compile-time reflection and (although it didn't have a name at the time) design by introspection, I got pretty annoyed at having to debug why my template constraints failed. So I had an idea and it's been waiting for me to write a DIP ever since. I don't think I have time right now to write the DIP properly, but given the recent discussion, here's a sketch. Say I have: void func(R)(R range) if(isInputRange!R) { ... } Maybe I didn't even write the function, maybe it's in Phobos. But I'm interested in implementing an input range but this fails to instantiate. I have no idea why. Now, it's usually good practice to include a `static assert(isInputRange!MyType)` somewhere, but if that fails... you have no idea why. Was it `popFront`? `front`? `empty`? Why didn't it compile? I've resorted to copying the code from the lambda in the customary `is(typeof({...}))` in the template constraint and forcing the compiler to give the message that it's hiding from me. The compiler knows why the static assert failed, but it won't tell me unless I force it to. This is analogous to dynamic dispatch except, in that case, the compiler will quite gladly tell you if you forgot to implement a virtual function and much more besides. And why can it do that? Because you told it your class implements an interface or inherits from a class with abstract functions / methods. So... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... } or struct MyRange: static isInputRange { ... } // that way classes could do this too Then instead of: foo.d(9): Error: static assert (isInputRange!(MyRange)) is false I'd get: foo.d(9): Error: MyRange does not implement popFront I'd hoped to paste the result of trying to compile isInputRange in the 2nd example but apparently right now I get the very horrible: foo.d(17): Error: template std.range.primitives.popFront cannot deduce function from argument types !()(MyRange), candidates are: /usr/include/dlang/dmd/std/range/primitives.d(1992): std.range.primitives.popFront(T)(ref T[] a) if (!isNarrowString!(T[]) && !is(T[] == void[])) /usr/include/dlang/dmd/std/range/primitives.d(2015): std.range.primitives.popFront(C)(ref C[] str) if (isNarrowString!(C[])) foo.d(22): Error: template instance foo.func!(MyRange) error instantiating At least it mentions `popFront`. Anyway, you understand what I want from this. The only I think I've seen that comes close to achieving this is a massive hack abusing __ctfe in Adam's book. I've lost count of how many times a template constraint failed because of some stupid thing like safe functions not being able to call system ones and it was really hard figuring out why. I don't want C++ concepts. I just want compiler error messages pointing me in the general direction of why my type fails a template constraint. This is especially bad when there are several overloads with different constraints, I usually have to comment out code until the offending one presents itself. AtilaI was thinking about same many times before. With one difference: instead of this: struct MyRange: isInputRange { ... } I would use something like this: interface(InputRange, ...) struct MyRange { ... } interface(InputRange, ...) class MyClassRange { ... }
Jul 28 2015
On Tuesday, 28 July 2015 at 13:23:37 UTC, Daniel Kozák wrote:I would use something like this: interface(InputRange, ...) struct MyRange { ... } interface(InputRange, ...) class MyClassRange { ... }I get the change from isInputRange to InputRange, because isInputRange is like a templated bool or something and InputRange is an interface. But why the suggestion of user-defined attributes? As an aside, I'm still trying to understand the reasoning behind some of the decisions around structs and inheritance. The big difference between structs and classes is that structs are value types and classes are reference types. I'm not entirely sure on all the implications of that. My guess is that it helps to know how big a value type is in advance (at compilation). So if you're inheriting virtual methods in a struct, then you won't necessarily know how big it will be and that can be problem. However, I would think that final and static methods wouldn't be an issue. Coming around to interfaces, they only allow virtual methods without implementation, plus final and static methods. I would think the last two would work fine with structs because they should be known at compile time, so the only part I'm not sure about is the way the virtual methods without implementation would work. It seems like to implement what the OP is suggesting you'd need some separate rule for handling struct inheritance of interfaces so that they are not virtual in the same manner as they would be for classes.
Jul 28 2015
On Tuesday, 28 July 2015 at 13:23:37 UTC, Daniel Kozák wrote:I was thinking about same many times before. With one difference: instead of this: struct MyRange: isInputRange { ... } I would use something like this: interface(InputRange, ...) struct MyRange { ... } interface(InputRange, ...) class MyClassRange { ... }I have actually thought about this as well, and a thing that could actually make this possible is if UDAs could get the thing they are attached to. I realized it could be done easily when I saw that this worked: template someuda(alias attach) {} someuda!(x) int x; someuda can even inspect x and see itself attached to it, pretty cool. Only thing that would be needed to make it seamless is a for a new default arg value identifier like __FILE__ that just translates to whatever the UDA is attached to. I would call it __UDA_ATTACHMENT__. So the above would be translated to: template someuda(alias attach = __UDA_ATTACHMENT__) {} someuda!() int x; The parens would sadly still be necessary, but then you could do: import std.range.interfaces; template StaticInterface(alias Iface, alias attach = __UDA_ATTACHMENT__) if(is(Iface == interface)) { // See if attach statically implements Iface } StaticInterface!(InputRange) struct someRange {...}
Jul 28 2015
On Tuesday, 28 July 2015 at 18:23:02 UTC, Tofu Ninja wrote:On Tuesday, 28 July 2015 at 13:23:37 UTC, Daniel Kozák wrote:+1 automatic UDA attachment is something I've wanted ever since i started using D. and their would be other uses for automatic UDA attachment than for testing structs.[...]I have actually thought about this as well, and a thing that could actually make this possible is if UDAs could get the thing they are attached to. I realized it could be done easily when I saw that this worked: [...]
Jul 30 2015
On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:So... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... } or struct MyRange: static isInputRange { ... } // that way classes could do this too+1 Concept like this can help a lot of people will not be scared off by the complexity of Dlang
Jul 28 2015
On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:So... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... }+1or struct MyRange: static isInputRange { ... } // that way classes could do this tooWhat about this instead: satisfies(isInputRange) struct MyRange { ... } which is not as terse, but maybe less confusing, because intuitively ':' could be mistaken to mean 'extends'. 'static' has too many meanings already for my taste. I really don't like it when frequently used keywords are reused to mean different things in slightly different places.
Jul 29 2015
On Wednesday, 29 July 2015 at 08:25:04 UTC, Roland Hadinger wrote:On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:That looks nice, but who's going to check it? UDAs have to be reflected on to well, do anything. At least a template mixin will cause a static assert to fail. AtilaSo... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... }+1or struct MyRange: static isInputRange { ... } // that way classes could do this tooWhat about this instead: satisfies(isInputRange) struct MyRange { ... } which is not as terse, but maybe less confusing, because intuitively ':' could be mistaken to mean 'extends'. 'static' has too many meanings already for my taste. I really don't like it when frequently used keywords are reused to mean different things in slightly different places.
Jul 29 2015
On Wednesday, 29 July 2015 at 14:51:52 UTC, Atila Neves wrote:On Wednesday, 29 July 2015 at 08:25:04 UTC, Roland Hadinger wrote:If you write: satisfies!(isInputRange, MyRange) struct MyRange { ... } the UDA can check it self, it really works as expected. Which is why I suggested a way to get whatever the UDA is attached to automatically in my other post.On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:That looks nice, but who's going to check it? UDAs have to be reflected on to well, do anything. At least a template mixin will cause a static assert to fail. AtilaSo... instead of having traits / concepts, what I wanted from D is to be able to do this: struct MyRange: isInputRange { ... }+1or struct MyRange: static isInputRange { ... } // that way classes could do this tooWhat about this instead: satisfies(isInputRange) struct MyRange { ... } which is not as terse, but maybe less confusing, because intuitively ':' could be mistaken to mean 'extends'. 'static' has too many meanings already for my taste. I really don't like it when frequently used keywords are reused to mean different things in slightly different places.
Jul 29 2015
On Wednesday, 29 July 2015 at 20:26:53 UTC, Tofu Ninja wrote:If you write: satisfies!(isInputRange, MyRange) struct MyRange { ... } the UDA can check it self, it really works as expected. Which is why I suggested a way to get whatever the UDA is attached to automatically in my other post.A UDA can reference the thing it's being attached to with no problems. For example, this works 100% as expected right now... UDA!testS struct testS // UDA fails to instantiate because testS is not an inputRange! { } template UDA(alias a) { import std.range; static assert(isInputRange!a); } The only thing that would be needed to make this a nice solution is some syntax sugar to automatically get whatever the UDA is attached to, which is why I suggested this: template UDA(alias a = __UDA_ATTACHMENT__) { ... }
Jul 29 2015
On Wednesday, 29 July 2015 at 20:41:02 UTC, Tofu Ninja wrote:On Wednesday, 29 July 2015 at 20:26:53 UTC, Tofu Ninja wrote:You still wouldn't get a better error message here than with: struct MyRange { ... static assert(isInputRange!MyRange); } It's less typing, but you still wouldn't know why the static assertion failed. Now, if we make Adam's idiom in my aforementioned pull request the go-to thing, then this would be good: satisfies!(checkInputRange, MyRange) struct MyRange { ... } There's still the problem of having two names for each constraint: checkInputRange and isInputRange. But... we could also establish the convention of checkXXX and use string mixins to turn this: satifies!(isInputRange, MyRange) into (which works cos I just tried it): template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } Now, _this_ I could go for. AtilaIf you write: satisfies!(isInputRange, MyRange) struct MyRange { ... } the UDA can check it self, it really works as expected. Which is why I suggested a way to get whatever the UDA is attached to automatically in my other post.A UDA can reference the thing it's being attached to with no problems. For example, this works 100% as expected right now... UDA!testS struct testS // UDA fails to instantiate because testS is not an inputRange! { } template UDA(alias a) { import std.range; static assert(isInputRange!a); } The only thing that would be needed to make this a nice solution is some syntax sugar to automatically get whatever the UDA is attached to, which is why I suggested this: template UDA(alias a = __UDA_ATTACHMENT__) { ... }
Jul 30 2015
On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:You still wouldn't get a better error message here than with: struct MyRange { ... static assert(isInputRange!MyRange); } It's less typing, but you still wouldn't know why the static assertion failed. Now, if we make Adam's idiom in my aforementioned pull request the go-to thing, then this would be good: satisfies!(checkInputRange, MyRange) struct MyRange { ... } There's still the problem of having two names for each constraint: checkInputRange and isInputRange. But... we could also establish the convention of checkXXX and use string mixins to turn this: satifies!(isInputRange, MyRange) into (which works cos I just tried it): template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } Now, _this_ I could go for. AtilaI might be overthinking this. You would still need to define something for checkInputRange. Are you thinking something simple like template checkInputRange(R) { enum bool checkInputRange = isInputRange!R; } In this case, the error message is still referring to checkInputRange instead of isInputRange. So the user would still need to refer to that and see what that functions conditions are. Alternately, you could write checkInputRange to provide the user information about which functions are not implemented (like no pop, no popFront, no empty). That seems more difficult, but would be convenient for the user. The benefit of something simple like template satisfies_alt(alias Constraint, R) { static assert(Constraint!R); } is that at least you don't have to write extra functions. You only need isInputRange. The downside is that the error message references Constraint instead of isInputRange. That's less intuitive.
Jul 30 2015
On Thursday, 30 July 2015 at 19:12:27 UTC, jmh530 wrote:On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:Check the out the PR I mentioned: https://github.com/D-Programming-Language/phobos/pull/3520 The idea would be to define a similar check function for each constraint. Without it, you don't get the nice error messages. Atila[...]I might be overthinking this. You would still need to define something for checkInputRange. Are you thinking something simple like template checkInputRange(R) { enum bool checkInputRange = isInputRange!R; } In this case, the error message is still referring to checkInputRange instead of isInputRange. So the user would still need to refer to that and see what that functions conditions are. Alternately, you could write checkInputRange to provide the user information about which functions are not implemented (like no pop, no popFront, no empty). That seems more difficult, but would be convenient for the user. The benefit of something simple like template satisfies_alt(alias Constraint, R) { static assert(Constraint!R); } is that at least you don't have to write extra functions. You only need isInputRange. The downside is that the error message references Constraint instead of isInputRange. That's less intuitive.
Jul 31 2015
On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:There's still the problem of having two names for each constraint: checkInputRange and isInputRange. But... we could also establish the convention of checkXXX and use string mixins to turn this: satifies!(isInputRange, MyRange) into (which works cos I just tried it): template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } Now, _this_ I could go for. AtilaWhy are there two different things in the first place? satisfies!(myConcept, T) should test the constraints and give a sensible error message. This you use to indicate that type T implements myConcept. Why can't another template use the very same concept information to check if a type implements the concept? e.g.: satisfies!(myConcept, MyStruct) struct MyStruct { /* ... */ } void foo(T)(T t) if (check!(myConcept, T)) { /* ... */ }
Jul 31 2015
On Friday, 31 July 2015 at 11:16:48 UTC, Biotronic wrote:Why are there two different things in the first place? satisfies!(myConcept, T) should test the constraints and give a sensible error message. This you use to indicate that type T implements myConcept. Why can't another template use the very same concept information to check if a type implements the concept? e.g.: satisfies!(myConcept, MyStruct) struct MyStruct { /* ... */ } void foo(T)(T t) if (check!(myConcept, T)) { /* ... */ }That would be better !
Jul 31 2015
On Friday, 31 July 2015 at 11:16:48 UTC, Biotronic wrote:On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:Because you want to: 1. Check your type and get sensible error messages 2. Use a constraint in function declarations You can't have ` satisfies!(isInputRange, T)` give you an error message if it doesn't have access to the source code of the lambda that actually does the checking and you can't get error messages unless you force the compilation of code you _know_ won't compile. It's ok for template constraints to be false without causing a compilation error; that's how we get compile-time template function resolution. When you're using something like ` satisfies` however, you want a compilation error and its message. That's why you need two things. AtilaThere's still the problem of having two names for each constraint: checkInputRange and isInputRange. But... we could also establish the convention of checkXXX and use string mixins to turn this: satifies!(isInputRange, MyRange) into (which works cos I just tried it): template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } Now, _this_ I could go for. AtilaWhy are there two different things in the first place? satisfies!(myConcept, T) should test the constraints and give a sensible error message. This you use to indicate that type T implements myConcept. Why can't another template use the very same concept information to check if a type implements the concept? e.g.: satisfies!(myConcept, MyStruct) struct MyStruct { /* ... */ } void foo(T)(T t) if (check!(myConcept, T)) { /* ... */ }
Jul 31 2015
On Friday, 31 July 2015 at 14:23:28 UTC, Atila Neves wrote:On Friday, 31 July 2015 at 11:16:48 UTC, Biotronic wrote:Notice however, that (again, in the PR), the "real" definition happens only once, in the check function. Then the constraint is always a trivial `enum MyConstraint = is(typeof(myCheckFunction));`. It's just a matter of moving the lambda away, giving it a name, and doing the weird `if(!__ctfe)` hack. AtilaOn Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:Because you want to: 1. Check your type and get sensible error messages 2. Use a constraint in function declarations You can't have ` satisfies!(isInputRange, T)` give you an error message if it doesn't have access to the source code of the lambda that actually does the checking and you can't get error messages unless you force the compilation of code you _know_ won't compile. It's ok for template constraints to be false without causing a compilation error; that's how we get compile-time template function resolution. When you're using something like ` satisfies` however, you want a compilation error and its message. That's why you need two things. Atila[...]Why are there two different things in the first place? satisfies!(myConcept, T) should test the constraints and give a sensible error message. This you use to indicate that type T implements myConcept. Why can't another template use the very same concept information to check if a type implements the concept? e.g.: satisfies!(myConcept, MyStruct) struct MyStruct { /* ... */ } void foo(T)(T t) if (check!(myConcept, T)) { /* ... */ }
Jul 31 2015
On Friday, 31 July 2015 at 14:24:58 UTC, Atila Neves wrote:Notice however, that (again, in the PR), the "real" definition happens only once, in the check function. Then the constraint is always a trivial `enum MyConstraint = is(typeof(myCheckFunction));`. It's just a matter of moving the lambda away, giving it a name, and doing the weird `if(!__ctfe)` hack. AtilaLooking at the PR also resolved my earlier question. Running the code as below (do not import std.range) will tell you exactly what isn't implemented from isInputRange (in this case, I commented out popFront). Very cool. template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } template isInputRange(R) { enum bool isInputRange = is(typeof(checkInputRange!R)); } bool checkInputRange(R)(inout int = 0) { if (__ctfe) { R r = R.init; // 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 } return true; } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; //void popFront() {} property int front() { return 0; } } void main() { Zeroes Z; }
Jul 31 2015
On Friday, 31 July 2015 at 16:28:30 UTC, jmh530 wrote:Looking at the PR also resolved my earlier question. Running the code as below (do not import std.range) will tell you exactly what isn't implemented from isInputRange (in this case, I commented out popFront). Very cool. template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } template isInputRange(R) { enum bool isInputRange = is(typeof(checkInputRange!R)); } bool checkInputRange(R)(inout int = 0) { if (__ctfe) { R r = R.init; // 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 } return true; } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; //void popFront() {} property int front() { return 0; } } void main() { Zeroes Z; }Nice. Here are the actual error messages for the record: /d125/f236.d(18): Error: no property 'popFront' for type 'Zeroes' /d125/f236.d(24): Error: template instance f236.satisfies!(isInputRange, Zeroes) error instantiating Piotrek
Aug 01 2015
On Friday, 31 July 2015 at 14:23:28 UTC, Atila Neves wrote:On Friday, 31 July 2015 at 11:16:48 UTC, Biotronic wrote:He does have a point. isInputRange!Bar = (checkInputRange!Bar == true);Why can't another template use the very same concept information to check if a type implements the concept? e.g.: satisfies!(myConcept, MyStruct) struct MyStruct { /* ... */ } void foo(T)(T t) if (check!(myConcept, T)) { /* ... */ }Because you want to: 1. Check your type and get sensible error messages 2. Use a constraint in function declarations It's ok for template constraints to be false without causing a compilation error; that's how we get compile-time template function resolution. When you're using something like ` satisfies` however, you want a compilation error and its message. That's why you need two things. Atila
Jul 31 2015
On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves wrote:On Wednesday, 29 July 2015 at 20:41:02 UTC, Tofu Ninja wrote:The one thing I'm dissatisfied with this proposition is “isInputRange”. Implementing it requires checking that the struct has some methods, and checking that means that “isInputRange” has the information about what methods it should have, and the name suggests that it is done imperatively. Don't we already have something to describe what methods an (conceptual) object should have? Yes we do, that's interfaces and they are declarative which is nice. They are great as reference and for documentation, better than a succession of “static if”. The problem with interfaces is that one has to inherit from them. I think “isInputRange” in itself is a bad idea hardly reusable. What I would like to see is a way to take an interface and implicitely turn it into an interface checker that would statically check a struct. I'd like to turn: satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } into satisfies!(InputRangeInterface /*whatever the name */, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } That would be great to get people to properly document their interfaces (good for documentation) without the drawback of having to inherit from them, while still being available for normal interfaces and without any information redundancy. However, I'm not sure that's possible today... What do you think about it?On Wednesday, 29 July 2015 at 20:26:53 UTC, Tofu Ninja wrote:You still wouldn't get a better error message here than with: struct MyRange { ... static assert(isInputRange!MyRange); } It's less typing, but you still wouldn't know why the static assertion failed. Now, if we make Adam's idiom in my aforementioned pull request the go-to thing, then this would be good: satisfies!(checkInputRange, MyRange) struct MyRange { ... } There's still the problem of having two names for each constraint: checkInputRange and isInputRange. But... we could also establish the convention of checkXXX and use string mixins to turn this: satifies!(isInputRange, MyRange) into (which works cos I just tried it): template satisfies(alias Constraint, R) { enum check = "check" ~ Constraint.stringof[2..$-3] ~ "!(R)"; enum assert_ = "static assert("~ check ~ ");"; mixin(assert_); //mixes in "static assert(checkInputRange!R)" } satisfies!(isInputRange, Zeroes) struct Zeroes { enum empty = false; void popFront() {} property int front() { return 0; } } Now, _this_ I could go for. AtilaIf you write: satisfies!(isInputRange, MyRange) struct MyRange { ... } the UDA can check it self, it really works as expected. Which is why I suggested a way to get whatever the UDA is attached to automatically in my other post.A UDA can reference the thing it's being attached to with no problems. For example, this works 100% as expected right now... UDA!testS struct testS // UDA fails to instantiate because testS is not an inputRange! { } template UDA(alias a) { import std.range; static assert(isInputRange!a); } The only thing that would be needed to make this a nice solution is some syntax sugar to automatically get whatever the UDA is attached to, which is why I suggested this: template UDA(alias a = __UDA_ATTACHMENT__) { ... }
Jul 31 2015
On 7/29/15 4:25 AM, Roland Hadinger wrote:satisfies(isInputRange) struct MyRange { ... }This is straight from the "creative use of what we have" mantra that I think is worth exploring. -- Andrei
Jul 29 2015
On Tuesday, 28 July 2015 at 12:49:17 UTC, Atila Neves wrote:So I missed the boat on the lengthy Rust traits discussion on the other thread. I confess I didn't have time to read all of it so forgive me if I bring up something that's already been said there. [...]I went back to my copy of Adam's book and made a PR based on it: https://github.com/D-Programming-Language/phobos/pull/3520 Doesn't require a language change and way better than the status quo. But still hacky. Atila
Jul 29 2015