digitalmars.D - Compile-time Interfaces
- Kapps (88/88) Nov 26 2011 One of the great things about D is the ability to do so much work at
- Andrei Alexandrescu (4/29) Nov 26 2011 What's "auto" here? This thing alone pretty much destroys the idea;
- Timon Gehr (9/39) Nov 26 2011 I would solve that similar to this:
- Jacob Carlborg (10/52) Nov 27 2011 Then what would "T" be? Seems to me it needs to look something like this...
- Timon Gehr (5/62) Nov 27 2011 That is an overly restrictive interface because the element type is fixe...
- Jesse Phillips (2/7) Nov 28 2011 void foo(T, R : Range!T)(R input) {} // ?
- Timon Gehr (4/11) Nov 29 2011 Templates are turing complete. This kind of matching is undecideable,
- Kapps (14/27) Nov 26 2011 At this point, my solutions may not be particularly useful/feasible
- Jacob Carlborg (12/42) Nov 27 2011 "auto" cannot be used here. Just like it can't be used in any place
- Timon Gehr (3/45) Nov 27 2011 The element type is not a parameter to the range interface. See
- Jacob Carlborg (4/20) Nov 27 2011 Ah, you're right, my bad.
- Andrei Alexandrescu (15/24) Nov 27 2011 That seems helpful, but it doesn't lead on a good path, for several reas...
- Jacob Carlborg (6/32) Nov 27 2011 For the simpler cases an interface is easier to reason about. But yes,
- Andrei Alexandrescu (14/16) Nov 27 2011 I've reached the same conclusion. Symbolic interfaces explode quite
- =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= (19/50) Nov 27 2011 Debatable, what kind of useful function can be built operating on
- Andrei Alexandrescu (16/44) Nov 27 2011 It is simpler syntactically and semantically to say "function works for
- deadalnix (4/7) Nov 27 2011 I do think that this is not helping the language itself, that's a point.
- =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= (96/151) Nov 28 2011 uch
- =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= (78/82) Nov 28 2011 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D...
- Caligo (1/1) Nov 26 2011 It's almost 2012, and people are still using IDE's?
- Timon Gehr (2/3) Nov 26 2011 =)
- Paulo Pinto (2/3) Nov 27 2011 Some people like to be productive...
- =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= (3/4) Nov 27 2011 Let's not go there.
- Jacob Carlborg (8/96) Nov 27 2011 I have always wonder why D doesn't have any kind of interface type that
- =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= (5/93) Nov 27 2011 I think 'static interface' or 'template interface' or something like
- so (5/39) Nov 27 2011 I sort of like the current solution, (it uses the language's most powerf...
- Marco Leise (2/2) Nov 28 2011 Sounds familiar :D
- Andrei Alexandrescu (4/6) Nov 28 2011 Not to mention
One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); } Much nicer than this very confusing looking statement (taken from std.range): template isInputRange(R) { enum bool isInputRange = is(typeof( { R r; // 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 then instead of returning auto, you could return Range if you wanted to, or OutputRange, etc. This gives much more info by just looking at the signature of the method, as opposed to comb through the documentation to find out what this mysterious type is that it returns. Another useful thing is that new types could now actually have it be statically verified that they implement this feature: struct MyRange(T) { T popFront() const { } property bool empty() const { } property ref T front() { } } When you try to use this in a method that takes in a type T where isInputRange!(T), you would get a confusing message saying no suitable overloads found. If you had, this however: struct MyRange(T) : (static|enum)? Range { T popFront() const { } property bool empty() const { } property ref T front() { } } You would see a message saying that it can't implement Range because popFront returns T instead of void. Not only that, but a good IDE will also offer to implement the Range signatures for you, like Visual Studio The main issue I can think of is when a method takes in multiple different types that implement the same static interface. An example: Range Zip(Range First, Range Second); Would First/Second be the same type? Does it matter? Should the compiler handle it? What about the return type? An alternate way though would just be to (when there are multiple types of the same interface) force the use of: Range Zip(R1 : Range, R2 : Range)(R1 First, R2) Second; An IDE will still know that these are ranges. You still get the readability of it. You still get all the benefits of the ranges. You just have to make Zip a template method anyways. The other issue I can think of is that this could potentially make someone believe that methods that take in / return a Range aren't template methods when they actually are. Of course, in order for that to matter, they have to realize in the first place that a template method creates multiple instances of the method while caring about the overhead that creates. Overall though, I think that this would be a huge benefit to D's compile time capability (not to mention learning ranges), while incurring no run-time cost. It also makes it much nicer when your IDE now knows what the types you're working with are. There are already IDEs that can take advantage of the above benefits, and only more will come. Plus, it's much much nicer to just be able to look at the interface rather than figuring out the documentation for, say, a range (and many editors/ides will offer a go-to-definition to do this for you). Template types / figuring out what they are is the messiest thing in D at the moment (IMO), and this would be a nice way of solving it for many situations. Thoughts?
Nov 26 2011
On 11/26/11 6:40 PM, Kapps wrote:One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 26 2011
On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:On 11/26/11 6:40 PM, Kapps wrote:I would solve that similar to this: enum interface Range { private alias T; // could also use 'static' instead of 'private' :o) void popFront(); property bool empty() const; property T front(); } Where "T" does not actually contribute to the imposed interface.One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 26 2011
On 2011-11-27 02:13, Timon Gehr wrote:On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:Then what would "T" be? Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); } -- /Jacob CarlborgOn 11/26/11 6:40 PM, Kapps wrote:I would solve that similar to this: enum interface Range { private alias T; // could also use 'static' instead of 'private' :o) void popFront(); property bool empty() const; property T front(); } Where "T" does not actually contribute to the imposed interface.One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 27 2011
On 11/27/2011 12:33 PM, Jacob Carlborg wrote:On 2011-11-27 02:13, Timon Gehr wrote:Any symbol. It is determined by deduction.On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:Then what would "T" be?On 11/26/11 6:40 PM, Kapps wrote:I would solve that similar to this: enum interface Range { private alias T; // could also use 'static' instead of 'private' :o) void popFront(); property bool empty() const; property T front(); } Where "T" does not actually contribute to the imposed interface.One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. AndreiSeems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }That is an overly restrictive interface because the element type is fixed. The interface should be usable like this: void foo(R : Range)(R input) { /* ... * / }
Nov 27 2011
On Sun, 27 Nov 2011 12:54:48 +0100, Timon Gehr wrote:That is an overly restrictive interface because the element type is fixed. The interface should be usable like this: void foo(R : Range)(R input) { /* ... * / }void foo(T, R : Range!T)(R input) {} // ?
Nov 28 2011
On 11/29/2011 04:20 AM, Jesse Phillips wrote:On Sun, 27 Nov 2011 12:54:48 +0100, Timon Gehr wrote:Templates are turing complete. This kind of matching is undecideable, and there exist cases where it is even ambiguous: enum interface X(T){ } // what will T be deduced as?That is an overly restrictive interface because the element type is fixed. The interface should be usable like this: void foo(R : Range)(R input) { /* ... * / }void foo(T, R : Range!T)(R input) {} // ?
Nov 29 2011
On 26/11/2011 7:03 PM, Andrei Alexandrescu wrote:On 11/26/11 6:40 PM, Kapps wrote:At this point, my solutions may not be particularly useful/feasible because I am not experienced in compiler writing. My thoughts though, would be that because the interface is essentially compile-time verification, that the interface would never truly be part of the class. Then, when the compiler tries to verify it implements the interface, it checks the signatures for static methods replacing auto with any type (perhaps excluding void). This way auto does not need an actual value for the interface, but is still transparent to the user. But this still wouldn't solve the issue of when you have multiple types that should be the same value. An alias, similar to Timon's suggestion, could be used. Another option is something like: property auto front(); property typeof(front) getAndPopFront();auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 26 2011
On 2011-11-27 02:03, Andrei Alexandrescu wrote:On 11/26/11 6:40 PM, Kapps wrote:"auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); } -- /Jacob CarlborgOne of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 27 2011
On 11/27/2011 12:36 PM, Jacob Carlborg wrote:On 2011-11-27 02:03, Andrei Alexandrescu wrote:The element type is not a parameter to the range interface. See 'isInputRange'.On 11/26/11 6:40 PM, Kapps wrote:"auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); }What's "auto" here? This thing alone pretty much destroys the idea; we've considered it very seriously. Andrei
Nov 27 2011
On 2011-11-27 12:56, Timon Gehr wrote:On 11/27/2011 12:36 PM, Jacob Carlborg wrote:Ah, you're right, my bad. -- /Jacob Carlborg"auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }The element type is not a parameter to the range interface. See 'isInputRange'.
Nov 27 2011
On 11/27/11 5:36 AM, Jacob Carlborg wrote:"auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }That seems helpful, but it doesn't lead on a good path, for several reasons. 1. Right now we have "function applies to any type R that is a range". With the other approach, there'd be "function applies to any type T such that the given type R is a Range!T". That roundabout approach is likely to scale poorly to more complex cases. It's arguably inferior because often the range-ness is of interest, not naming T. 2. Restrictions can be any Boolean expression, whereas interfaces only apply to types. 3. In an interface-based approach, everything must be named; there are no optional properties such as hasLength or isInfinite. That could, of course, be added as restricted templates, which means interfaces must coexist with restricted templates, a more powerful feature. So in the end interfaces are redundant. Andrei
Nov 27 2011
On 2011-11-27 16:47, Andrei Alexandrescu wrote:On 11/27/11 5:36 AM, Jacob Carlborg wrote:I was missinterpreting the isInputRange template."auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }That seems helpful, but it doesn't lead on a good path, for several reasons. 1. Right now we have "function applies to any type R that is a range". With the other approach, there'd be "function applies to any type T such that the given type R is a Range!T". That roundabout approach is likely to scale poorly to more complex cases. It's arguably inferior because often the range-ness is of interest, not naming T.2. Restrictions can be any Boolean expression, whereas interfaces only apply to types. 3. In an interface-based approach, everything must be named; there are no optional properties such as hasLength or isInfinite. That could, of course, be added as restricted templates, which means interfaces must coexist with restricted templates, a more powerful feature. So in the end interfaces are redundant.For the simpler cases an interface is easier to reason about. But yes, template constraints are more powerful. -- /Jacob Carlborg
Nov 27 2011
On 11/27/11 10:03 AM, Jacob Carlborg wrote:For the simpler cases an interface is easier to reason about. But yes, template constraints are more powerful.I've reached the same conclusion. Symbolic interfaces explode quite quickly. Fortunately, the experimentation in that direction has been already done in the context of C++ concepts. We designed restricted templates as a simple method to address the same problem that C++ concepts do, and restricted templates have served D exceedingly well. There are ways to address the issue "why did type X not satisfy constraint E on a template?" at both compiler and library level. The compiler could e.g. explain which expressions in a combination of conjunctions and disjunctions has failed. A library could define a catch-all function that issues the appropriate error message (though this is rather laborious). I personally think the compiler-based approach can be very fertile. Andrei
Nov 27 2011
Andrei Alexandrescu wrote:On 11/27/11 5:36 AM, Jacob Carlborg wrote:h"auto" cannot be used here. Just like it can't be used in any place where there is no implementation of a function. Seems to me it needs to look something like this: enum interface Range (T) { void popFront(); property bool empty() const; property T front(); }=20 That seems helpful, but it doesn't lead on a good path, for several reasons. =20 1. Right now we have "function applies to any type R that is a range". With the other approach, there'd be "function applies to any type T suc=that the given type R is a Range!T". That roundabout approach is likely=to scale poorly to more complex cases. It's arguably inferior because often the range-ness is of interest, not naming T. =20Debatable, what kind of useful function can be built operating on the above interface without reference to T (at least through auto)?2. Restrictions can be any Boolean expression, whereas interfaces only apply to types. =20Strawman! Nothing says that adding compile-time interfaces would remove boolean restrictions.3. In an interface-based approach, everything must be named; there are no optional properties such as hasLength or isInfinite. That could, of course, be added as restricted templates, which means interfaces must coexist with restricted templates, a more powerful feature. So in the end interfaces are redundant. =20Strawman again! Everything can be done in straight assembly so in the end so-called higher-level constructs are redundant. It should come down to a question of balance between individual feature simplicity, power, and overall language complexity. Adding a feature that is simpler and easier to use despite being less powerful may be a good thing (e.g. "foreach" vs. "while") although it increases the overall language complexity (additional keywords and concepts to understand and remember). Jerome --=20 mailto:jeberger free.fr http://jeberger.free.fr Jabber: jeberger jabber.fr
Nov 27 2011
On 11/27/11 12:36 PM, "Jérôme M. Berger" wrote:It is simpler syntactically and semantically to say "function works for any range R" and then optionally use ElementType!R, than to compulsively express both in one shot.1. Right now we have "function applies to any type R that is a range". With the other approach, there'd be "function applies to any type T such that the given type R is a Range!T". That roundabout approach is likely to scale poorly to more complex cases. It's arguably inferior because often the range-ness is of interest, not naming T.Debatable, what kind of useful function can be built operating on the above interface without reference to T (at least through auto)?What I meant to say was that since we need restricted templates anyway and compile-time interfaces would be a redundant addition to the language, we may as well question their usefulness.2. Restrictions can be any Boolean expression, whereas interfaces only apply to types.Strawman! Nothing says that adding compile-time interfaces would remove boolean restrictions.This is not helping the exchange.3. In an interface-based approach, everything must be named; there are no optional properties such as hasLength or isInfinite. That could, of course, be added as restricted templates, which means interfaces must coexist with restricted templates, a more powerful feature. So in the end interfaces are redundant.Strawman again!Everything can be done in straight assembly so in the end so-called higher-level constructs are redundant.This reduction of a reasonable argument doesn't, either.It should come down to a question of balance between individual feature simplicity, power, and overall language complexity. Adding a feature that is simpler and easier to use despite being less powerful may be a good thing (e.g. "foreach" vs. "while") although it increases the overall language complexity (additional keywords and concepts to understand and remember).Of course. But compile-time interfaces would need to stand on their own. We can't add such just because foreach makes things easier than while. Instead of ridiculing my arguments or engaging in fruitless comparisons, you may as well come with valid arguments in favor of compile-time interfaces. Thanks, Andrei
Nov 27 2011
Le 27/11/2011 21:14, Andrei Alexandrescu a écrit :What I meant to say was that since we need restricted templates anyway and compile-time interfaces would be a redundant addition to the language, we may as well question their usefulness.I do think that this is not helping the language itself, that's a point. But this would help IDE or any other thrid party tool. This also open the door to more understandable error message when it comes to template.
Nov 27 2011
Andrei Alexandrescu wrote:On 11/27/11 12:36 PM, "J=C3=A9r=C3=B4me M. Berger" wrote:=2E1. Right now we have "function applies to any type R that is a range"=uchWith the other approach, there'd be "function applies to any type T s=lythat the given type R is a Range!T". That roundabout approach is like=to scale poorly to more complex cases. It's arguably inferior because==20 It is simpler syntactically and semantically to say "function works for=often the range-ness is of interest, not naming T.Debatable, what kind of useful function can be built operating on the above interface without reference to T (at least through auto)?any range R" and then optionally use ElementType!R, than to compulsivel=yexpress both in one shot. =20y2. Restrictions can be any Boolean expression, whereas interfaces onl=e=20 What I meant to say was that since we need restricted templates anyway and compile-time interfaces would be a redundant addition to the language, we may as well question their usefulness. =20apply to types.Strawman! Nothing says that adding compile-time interfaces would remove boolean restrictions.3. In an interface-based approach, everything must be named; there ar=fno optional properties such as hasLength or isInfinite. That could, o=course, be added as restricted templates, which means interfaces must=coexist with restricted templates, a more powerful feature. So in the=Maybe because the argument wasn't so reasonable after all...=20 This is not helping the exchange. =20end interfaces are redundant.Strawman again!Everything can be done in straight assembly so in the end so-called higher-level constructs are redundant.=20 This reduction of a reasonable argument doesn't, either. =20=2EIt should come down to a question of balance between individual feature simplicity, power, and overall language complexity. Adding a feature that is simpler and easier to use despite being less powerful may be a good thing (e.g. "foreach" vs. "while") although it increases the overall language complexity (additional keywords and concepts to understand and remember).=20 Of course. But compile-time interfaces would need to stand on their own=We can't add such just because foreach makes things easier than while. Instead of ridiculing my arguments or engaging in fruitless comparisons=,you may as well come with valid arguments in favor of compile-time interfaces. =20You're missing my point. I don't have any strong opinion one way or another about compile-time interface, I'm simply pointing out that your arguments are not valid. The way I see it, your arguments can be resumed by "there is another way to do it, so there is no point in adding this functionality". This same argument could be applied to any high level construct and it weakens your overall argumentation. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D To get back on topic, your (valid) argument appears to be:since we need restricted templates anyway and compile-time interfaces would be a redundant addition to the language, we may [...] question their usefulness.So what would compile-time interfaces bring? - They're easier to specify and to understand (especially for new users or people coming from another language); - They could result in clearer compiler error messages; - For people using IDEs, the IDE should be able to generate automatically the required boilerplate to implement the interface, which it can't do for a template constraint. Even for those who do not use an IDE, the boilerplate may be gotten easily by copy-pasting the interface definition. On the flip side, what would compile-time interfaces cost? - Additional overall language complexity in the form of an additional language construct; - Development time to implement them. So, are the advantages worth the cost? =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D How would they work? It might be possible to implement compile-time interfaces with a simple lowering. Somebody who knows more about the compiler internals will have to say how feasible this idea is. Interface definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D The interface could be defined as a "static" template interface: static interface(T) Range { void popFront() const; property bool empty() const; property T front(); } Class definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D There would be nothing special when defining a class: class IntRange { void popFront() const; property bool empty() const; property int front(); } Function definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Simple case ----------- A function which uses a single compile-time interface and does not need access to the "internal" type(s) could be defined as: auto DoSomething (Range r) {...} This would be lowered to: auto DoSomething(R, T) (R r) if staticImplements!(R, Range!T) {...} Multiple arguments ------------------ If the function needs to access several compile-time interfaces, or if it needs access to the "internal" type(s), it could be defined as: auto DoSomething(T, U) (Range!T r1, Range!U r2) {...} This would be lowered to: auto DoSomething(R1, T, R2, U) (R1 r1, R2 r2) if (staticImplements!(R1, Range!T) && staticImplements!(R2, Range!U)) {...} Specific internal data type --------------------------- If the function requires a specific internal data type, it could be defined as: auto DoSomething (Range!int r) {...} This would be lowered to: auto DoSomething(R) (R r) if staticImplements!(R, Range!int) {...} staticImplements ---------------- Note that these lowerings use a "staticImplements" constraint. This constraint checks that a class (or interface) implements all of the methods defined in a static interface. It does not require that the class inherit from the interface (ISTR that somebody already wrote something similar for regular interfaces). Jerome --=20 mailto:jeberger free.fr http://jeberger.free.fr Jabber: jeberger jabber.fr
Nov 28 2011
J=C3=A9r=C3=B4me M. Berger wrote:=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D==3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=20 How would they work? =20On second thought, I think the "static" keyword (or whatever) should be added to the function declaration instead of the interface declaration. In effect, it would mean "I don't care what the actual type of this argument is provided that it provides the methods declared in the interface". There are two reasons why this is better: - I believe it is clearer for the user who looks at the function prototype, that way he sees immediately that something special is going on; - The lowerings I described can be done in the parsing pass without any semantic knowledge about the interfaces. The text below is the same as my previous description with the differences that compile-time interfaces are now standard interfaces and the change is in function declarations. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D How would they work? It might be possible to implement compile-time interfaces with a simple lowering. Somebody who knows more about the compiler internals will have to say how feasible this idea is. Interface definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D The interface could be defined as a standard template interface: interface(T) Range { void popFront() const; property bool empty() const; property T front(); } Class definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D There would be nothing special when defining a class: class IntRange { void popFront() const; property bool empty() const; property int front(); } Note that the class does not need to inherit from the interface. Function definition =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Simple case ----------- A function which uses a single compile-time interface and does not need access to the "internal" type(s) could be defined as: auto DoSomething (static Range r) {...} This would be lowered to: auto DoSomething(R, T) (R r) if staticImplements!(R, Range!T) {...} Multiple arguments ------------------ If the function needs to access several compile-time interfaces, or if it needs access to the "internal" type(s), it could be defined as: auto DoSomething(T, U) (static Range!T r1, static Range!U r2) {...} This would be lowered to: auto DoSomething(R1, T, R2, U) (R1 r1, R2 r2) if (staticImplements!(R1, Range!T) && staticImplements!(R2, Range!U)) {...} Specific internal data type --------------------------- If the function requires a specific internal data type, it could be defined as: auto DoSomething (static Range!int r) {...} This would be lowered to: auto DoSomething(R) (R r) if staticImplements!(R, Range!int) {...} staticImplements ---------------- Note that these lowerings use a "staticImplements" constraint. This constraint checks that a class (or interface) implements all of the methods defined in a static interface. It does not require that the class inherit from the interface (ISTR that somebody already wrote something similar for regular interfaces). Jerome --=20 mailto:jeberger free.fr http://jeberger.free.fr Jabber: jeberger jabber.fr
Nov 28 2011
It's almost 2012, and people are still using IDE's?
Nov 26 2011
On 11/27/2011 02:32 AM, Caligo wrote:It's almost 2012, and people are still using IDE's?=)
Nov 26 2011
Am 27.11.2011 02:32, schrieb Caligo:It's almost 2012, and people are still using IDE's?Some people like to be productive...
Nov 27 2011
On 27-11-2011 02:32, Caligo wrote:It's almost 2012, and people are still using IDE's?Let's not go there. - Alex
Nov 27 2011
On 2011-11-27 01:40, Kapps wrote:One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); } Much nicer than this very confusing looking statement (taken from std.range): template isInputRange(R) { enum bool isInputRange = is(typeof( { R r; // 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 then instead of returning auto, you could return Range if you wanted to, or OutputRange, etc. This gives much more info by just looking at the signature of the method, as opposed to comb through the documentation to find out what this mysterious type is that it returns. Another useful thing is that new types could now actually have it be statically verified that they implement this feature: struct MyRange(T) { T popFront() const { } property bool empty() const { } property ref T front() { } } When you try to use this in a method that takes in a type T where isInputRange!(T), you would get a confusing message saying no suitable overloads found. If you had, this however: struct MyRange(T) : (static|enum)? Range { T popFront() const { } property bool empty() const { } property ref T front() { } } You would see a message saying that it can't implement Range because popFront returns T instead of void. Not only that, but a good IDE will also offer to implement the Range signatures for you, like Visual Studio The main issue I can think of is when a method takes in multiple different types that implement the same static interface. An example: Range Zip(Range First, Range Second); Would First/Second be the same type? Does it matter? Should the compiler handle it? What about the return type? An alternate way though would just be to (when there are multiple types of the same interface) force the use of: Range Zip(R1 : Range, R2 : Range)(R1 First, R2) Second; An IDE will still know that these are ranges. You still get the readability of it. You still get all the benefits of the ranges. You just have to make Zip a template method anyways. The other issue I can think of is that this could potentially make someone believe that methods that take in / return a Range aren't template methods when they actually are. Of course, in order for that to matter, they have to realize in the first place that a template method creates multiple instances of the method while caring about the overhead that creates. Overall though, I think that this would be a huge benefit to D's compile time capability (not to mention learning ranges), while incurring no run-time cost. It also makes it much nicer when your IDE now knows what the types you're working with are. There are already IDEs that can take advantage of the above benefits, and only more will come. Plus, it's much much nicer to just be able to look at the interface rather than figuring out the documentation for, say, a range (and many editors/ides will offer a go-to-definition to do this for you). Template types / figuring out what they are is the messiest thing in D at the moment (IMO), and this would be a nice way of solving it for many situations. Thoughts?I have always wonder why D doesn't have any kind of interface type that can be used for this purpose, I like it. You could always force this: void foo (T : Range) (T t) {} Then one will know it's a template. -- /Jacob Carlborg
Nov 27 2011
On 27-11-2011 01:40, Kapps wrote:One of the great things about D is the ability to do so much work at compile-time, including static verification. An example is the amount of constraints available for templates, static ifs, etc. But at some point, it starts getting very problematic to just figure out what something does, and what something can do. An example for this, is ranges. They can be very confusing, and you don't know what they can do without actually going and looking up the exact definition of them. Even then, you have to know that the templated type a function expects is a range. Again, very confusing, and arguably messy. Finally, even now that you know what methods you can call on this mysterious type T, and you see that there's a clause isInputRange!(T) on the method, your IDE still has no clue about any of these things making it impossible to have semi-decent code completion for that type. Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); } Much nicer than this very confusing looking statement (taken from std.range): template isInputRange(R) { enum bool isInputRange = is(typeof( { R r; // 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 then instead of returning auto, you could return Range if you wanted to, or OutputRange, etc. This gives much more info by just looking at the signature of the method, as opposed to comb through the documentation to find out what this mysterious type is that it returns. Another useful thing is that new types could now actually have it be statically verified that they implement this feature: struct MyRange(T) { T popFront() const { } property bool empty() const { } property ref T front() { } } When you try to use this in a method that takes in a type T where isInputRange!(T), you would get a confusing message saying no suitable overloads found. If you had, this however: struct MyRange(T) : (static|enum)? Range { T popFront() const { } property bool empty() const { } property ref T front() { } } You would see a message saying that it can't implement Range because popFront returns T instead of void. Not only that, but a good IDE will also offer to implement the Range signatures for you, like Visual Studio The main issue I can think of is when a method takes in multiple different types that implement the same static interface. An example: Range Zip(Range First, Range Second); Would First/Second be the same type? Does it matter? Should the compiler handle it? What about the return type? An alternate way though would just be to (when there are multiple types of the same interface) force the use of: Range Zip(R1 : Range, R2 : Range)(R1 First, R2) Second; An IDE will still know that these are ranges. You still get the readability of it. You still get all the benefits of the ranges. You just have to make Zip a template method anyways. The other issue I can think of is that this could potentially make someone believe that methods that take in / return a Range aren't template methods when they actually are. Of course, in order for that to matter, they have to realize in the first place that a template method creates multiple instances of the method while caring about the overhead that creates. Overall though, I think that this would be a huge benefit to D's compile time capability (not to mention learning ranges), while incurring no run-time cost. It also makes it much nicer when your IDE now knows what the types you're working with are. There are already IDEs that can take advantage of the above benefits, and only more will come. Plus, it's much much nicer to just be able to look at the interface rather than figuring out the documentation for, say, a range (and many editors/ides will offer a go-to-definition to do this for you). Template types / figuring out what they are is the messiest thing in D at the moment (IMO), and this would be a nice way of solving it for many situations. Thoughts?I think 'static interface' or 'template interface' or something like that would make more sense. 'enum interface' just doesn't really make sense in this context. Other than that, seems like a reasonable idea to me. - Alex
Nov 27 2011
On Sun, 27 Nov 2011 02:40:08 +0200, Kapps <Kapps notvalidemail.com> wrote:Which brings in, compile-time interfaces. It seems like a natural thing to include when you have the above tools. Instead of having a method such as: auto DoSomething(T)(T Data) if(isInputRange!(T)) { } You could simply do: auto DoSomething(Range Data) { } where Range is defined as: enum interface Range { void popFront() const; property bool empty() const; property auto front(); } Much nicer than this very confusing looking statement (taken from std.range): template isInputRange(R) { enum bool isInputRange = is(typeof( { R r; // 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 }())); }I sort of like the current solution, (it uses the language's most powerful feature) but for such common use the syntax is ugly. As it was suggested many times (std.meta), we really need to simplify this to at least something like:template isInputRange(R) { enum bool isInputRange = compiles { R r; // 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 } }
Nov 27 2011
Sounds familiar :D http://www.digitalmars.com/d/archives/digitalmars/D/static_interface_for_structs_146478.html
Nov 28 2011
On 11/28/11 2:39 AM, Marco Leise wrote:Sounds familiar :D http://www.digitalmars.com/d/archives/digitalmars/D/static_interface_for_structs_146478.htmlNot to mention http://lists.puremagic.com/pipermail/digitalmars-d/2009-April/053098.html. Andrei
Nov 28 2011