www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Interfaces, traits, concepts, and my idea for a DIP

reply "Atila Neves" <atila.neves gmail.com> writes:
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
next sibling parent reply "Jack Stouffer" <jack jackstouffer.com> writes:
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
parent reply "Atila Neves" <atila.neves gmail.com> writes:
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:
 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?
I guess, but not easily. I've written template mixins to do that before and it was awkward. Atila
Jul 28 2015
parent reply "Kagamin" <spam here.lot> writes:
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
parent "Atila Neves" <atila.neves gmail.com> writes:
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:
 I guess, but not easily. I've written template mixins to do 
 that before and it was awkward.
What was awkward?
Writing a generic solution that would work for multiple constraints without code repetition. Atila
Jul 29 2015
prev sibling next sibling parent reply Daniel =?UTF-8?B?S296w6Fr?= <kozzi dlang.cz> writes:
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.
 
 Atila
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 { ... }
Jul 28 2015
next sibling parent "jmh530" <john.michael.hall gmail.com> writes:
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
prev sibling parent reply "Tofu Ninja" <emmons0 purdue.edu> writes:
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
parent "Sean Campbell" <sycam.inc gmail.com> writes:
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:
 [...]
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: [...]
+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.
Jul 30 2015
prev sibling next sibling parent "ChangLong" <changlon gmail.com> writes:
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
prev sibling next sibling parent reply "Roland Hadinger" <rolandh.dlangforum maildrop.cc> writes:
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 { ... }
+1
 or

 struct MyRange: static isInputRange { ... } // that way classes 
 could do this too
What 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
next sibling parent reply "Atila Neves" <atila.neves gmail.com> writes:
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:
 So... instead of having traits / concepts, what I wanted from 
 D is to be able to do this:

 struct MyRange: isInputRange { ... }
+1
 or

 struct MyRange: static isInputRange { ... } // that way 
 classes could do this too
What 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.
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. Atila
Jul 29 2015
parent reply "Tofu Ninja" <emmons0 purdue.edu> writes:
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:
 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 { ... }
+1
 or

 struct MyRange: static isInputRange { ... } // that way 
 classes could do this too
What 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.
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. Atila
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.
Jul 29 2015
parent reply "Tofu Ninja" <emmons0 purdue.edu> writes:
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
parent reply "Atila Neves" <atila.neves gmail.com> writes:
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:
 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__) { ... }
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. Atila
Jul 30 2015
next sibling parent reply "jmh530" <john.michael.hall gmail.com> writes:
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.

 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 30 2015
parent "Atila Neves" <atila.neves gmail.com> writes:
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:
 [...]
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.
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
Jul 31 2015
prev sibling next sibling parent reply "Biotronic" <simen.kjaras gmail.com> writes:
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.

 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
next sibling parent "cym13" <cpicard openmailbox.org> writes:
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
prev sibling parent reply "Atila Neves" <atila.neves gmail.com> writes:
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:
 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.

 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)) { /* ... */ }
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
Jul 31 2015
next sibling parent reply "Atila Neves" <atila.neves gmail.com> writes:
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:
 On Thursday, 30 July 2015 at 10:40:59 UTC, Atila Neves 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)) { /* ... */ }
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
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. Atila
Jul 31 2015
parent reply "jmh530" <john.michael.hall gmail.com> writes:
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.

 Atila
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; }
Jul 31 2015
parent "Piotrek" <nodata nodata.pl> writes:
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
prev sibling parent "Sebastiaan Koppe" <mail skoppe.eu> writes:
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:
 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
He does have a point. isInputRange!Bar = (checkInputRange!Bar == true);
Jul 31 2015
prev sibling parent "cym13" <cpicard openmailbox.org> writes:
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:
 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__) { ... }
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. Atila
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?
Jul 31 2015
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
prev sibling parent "Atila Neves" <atila.neves gmail.com> writes:
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