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
does for C#.

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

Reply via email to