www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Compile-time Interfaces

reply Kapps <Kapps NotValidEmail.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:
 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
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.
Nov 26 2011
parent reply Jacob Carlborg <doob me.com> writes:
On 2011-11-27 02:13, Timon Gehr wrote:
 On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:
 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
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.
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 Carlborg
Nov 27 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/27/2011 12:33 PM, Jacob Carlborg wrote:
 On 2011-11-27 02:13, Timon Gehr wrote:
 On 11/27/2011 02:03 AM, Andrei Alexandrescu wrote:
 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
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.
Then what would "T" be?
Any symbol. It is determined by deduction.
 Seems 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
parent reply Jesse Phillips <jessekphillips+d gmail.com> writes:
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
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/29/2011 04:20 AM, Jesse Phillips wrote:
 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) {} // ?
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?
Nov 29 2011
prev sibling next sibling parent Kapps <Kapps NotValidEmail.com> writes:
On 26/11/2011 7:03 PM, Andrei Alexandrescu wrote:
 On 11/26/11 6:40 PM, Kapps wrote:
 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
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();
Nov 26 2011
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2011-11-27 02:03, Andrei Alexandrescu wrote:
 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
"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 Carlborg
Nov 27 2011
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/27/2011 12:36 PM, Jacob Carlborg wrote:
 On 2011-11-27 02:03, Andrei Alexandrescu wrote:
 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
"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
parent Jacob Carlborg <doob me.com> writes:
On 2011-11-27 12:56, Timon Gehr wrote:
 On 11/27/2011 12:36 PM, 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();
 }
The element type is not a parameter to the range interface. See 'isInputRange'.
Ah, you're right, my bad. -- /Jacob Carlborg
Nov 27 2011
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2011-11-27 16:47, Andrei Alexandrescu wrote:
 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.
I was missinterpreting the isInputRange template.
 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
prev sibling parent reply =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= <jeberger free.fr> writes:
Andrei Alexandrescu wrote:
 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();
 }
=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=
h
 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.
=20
Debatable, 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.
=20
Strawman! 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.
=20
Strawman 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
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 11/27/11 12:36 PM, "Jérôme M. Berger" wrote:
 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)?
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.
 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.
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.
 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!
This is not helping the exchange.
 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
next sibling parent deadalnix <deadalnix gmail.com> writes:
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
prev sibling parent reply =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= <jeberger free.fr> writes:
Andrei Alexandrescu wrote:
 On 11/27/11 12:36 PM, "J=C3=A9r=C3=B4me M. Berger" wrote:
 1. Right now we have "function applies to any type R that is a range"=
=2E
 With the other approach, there'd be "function applies to any type T s=
uch
 that the given type R is a Range!T". That roundabout approach is like=
ly
 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)?
=20 It is simpler syntactically and semantically to say "function works for=
 any range R" and then optionally use ElementType!R, than to compulsivel=
y
 express both in one shot.
=20
 2. Restrictions can be any Boolean expression, whereas interfaces onl=
y
 apply to types.
Strawman! Nothing says that adding compile-time interfaces would remove boolean restrictions.
=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. =20
 3. In an interface-based approach, everything must be named; there ar=
e
 no optional properties such as hasLength or isInfinite. That could, o=
f
 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!
=20 This is not helping the exchange. =20
 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
Maybe because the argument wasn't so reasonable after all...
     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).
=20 Of course. But compile-time interfaces would need to stand on their own=
=2E
 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.
=20
You'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
parent =?UTF-8?B?IkrDqXLDtG1lIE0uIEJlcmdlciI=?= <jeberger free.fr> writes:
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?
=20
On 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
prev sibling next sibling parent reply Caligo <iteronvexor gmail.com> writes:
It's almost 2012, and people are still using IDE's?
Nov 26 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/27/2011 02:32 AM, Caligo wrote:
 It's almost 2012, and people are still using IDE's?
=)
Nov 26 2011
prev sibling next sibling parent Paulo Pinto <pjmlp progtools.org> writes:
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
prev sibling parent =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
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
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
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
prev sibling next sibling parent =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
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
prev sibling next sibling parent so <so so.so> writes:
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
prev sibling parent reply "Marco Leise" <Marco.Leise gmx.de> writes:
Sounds familiar :D
http://www.digitalmars.com/d/archives/digitalmars/D/static_interface_for_structs_146478.html
Nov 28 2011
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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.html
Not to mention http://lists.puremagic.com/pipermail/digitalmars-d/2009-April/053098.html. Andrei
Nov 28 2011