On Sat, 04 Apr 2009 01:13:09 +0400, Andrei Alexandrescu 
<[email protected]> wrote:

Christopher Wright wrote:
Andrei has put up a few enhancement requests in the bugzilla:
http://d.puremagic.com/issues/show_bug.cgi?id=2784
http://d.puremagic.com/issues/show_bug.cgi?id=2785
http://d.puremagic.com/issues/show_bug.cgi?id=2786

Yah, this too:

http://d.puremagic.com/issues/show_bug.cgi?id=2050

These are intended to change interfaces from being solely a tool of polymorphism into a way of specifying type constraints. The additions recommended can only be used at compile time (constructors, static functions, and nested types).

Yah. Let me add that I haven't thought the changes through, but I posted them to foster discussion, which is happening (thanks!).

I grant that it would be quicker and clearer to write:
interface Foo
{
   static void stuff();
   this (int);
}
template Template(Arg : Foo) {}
 than to write:
template Template(Arg) if (is (typeof (new Arg(0)) && isStaticFunction!(Arg, "stuff")) {}

Yah. Let me add that after the OOP meteoric rise in the past years, many of us can instantly figure how the former works, whereas the latter is thoroughly obscure.

Let me also pull the definition of a couple of range types in std.range (by the way, I should commit the new Phobos today):

template isInputRange(R)
{
     enum bool isInputRange = is(typeof(
     {
         R r;             // can define a range object
         if (r.empty) {}  // can test for empty
         r.next;          // can invoke next
         auto h = r.head; // can get the head of the range
     }()));
}

I wrote it as clearly as I could, and it doesn't quite look that pretty. I didn't even know the typeof({ ... }()) trick is possible; I saw it somewhere on this group, and took to it. Otherwise the code would have been even more convoluted (along the lines of what you wrote, just more of it). Furthermore, if a type is not a range, there's no simple way to output an error message telling exactly which test if failed. The only test possible is e.g. isInputRange!MyType.

I think we can't quite be happy that this is D's offering with regard to concepts. On the other hand, we'd like to avoid the aggravation of C++ concepts, which, in my humble opinion, have a very low power/complexity ratio. So I wanted to look at how interfaces can be reasonably extended into expressing concepts while still being strongly integrated with their classic runtime polymorphic role.

However, I'm not sure whether this would be used much at all, and it deserves some discussion.
 One detail of #2785 seems problematic:
interface Foo
{
    extern void bar(int, Foo, double);
}
meaning that a non-member function bar should exist that accepts an int, *the implementor of Foo*, and a double. This is a huge and silent divergence from the standard meaning of using the interface name; it would allow:
class C : Foo
{
    static void bar(int, C, double);
}
 and disallow:
class C : Foo
{
    static void bar(int, Foo, double);
}
Thoughts? Any concerns that I have not raised? I don't do sufficient metaprogramming to find any of this useful, I admit.

I thought of that, too. One option is to parameterize Foo with its subtype's name:

interface Foo(X)
{
     extern void bar(int, X, double);
}

Then you'd require C : Foo!C and so on. I'm not sure how important this detail is, but it is worth thinking of.

Another issue is that constructors in a struct are one thing, whereas in a class they are quite a different thing (references of a class can be copied!)


Andrei

I'm not sure I understand everything you intend so lets clear things a little.

I agree there should be a way to enforce struct to implement some.. contract.
But I believe you took a wrong way by using pseudo-interface inheritance.

First of all, it's called Concept in C++0x. I suggest to use the same name in 
D, too. (Yes, that's one more keyword, but this is just a discussion, right, 
and you didn't listen all my arguments yet).

concept Range(T)
{
   bool empty;
   T value;
   void next();
}

I don't think "void next;" is okay, besides, it's always a function anyway! On 
the other side, I can image an infinite range that returns constant values and next is a 
do nothing function, but still you can't have a member of type void.

Concept is different from interface. Concept says that if typeof(r) satisfies a 
Range concept, then the following syntax is allowed:

bool e = r.empty;
T v = r.value;
r.next();

But it doesn't say "how". r.empty may be a property, a (static) member, or even 
an enum. And that's exactly what's needed for Range definition!

On the contrary, interface defines a set of *virtual functions* that are 
invokable through an instance of that interface. That's why I don't support an 
idea of the following:

interface Foo
{
   this(int i);
   typedef Bar;
}

Foo foo = new Foo(42); // huh?
Foo.Bar x; //what's x.typeof?

BTW, why is(typeof(x)) is alowed but typeof(x) is not? I quite don't like 
x.typeof

Surely, you'll disallow both syntaxes, but this gets messy. Some of functions 
are callable through interface and some are only callable through a class that 
implements that interface!
Once again, I state that you you mistakenly mix two different concepts (pun is 
not inteded).

concept Foo
{
   int(int i);
   typedef Bar; // fine
}

Second, I believe structs *can* safely implement interfaces (hello, Weed!). 
They still shouldn't support inheritance, but *interface* inheritance:

interface Foo
{
   int bar();
}

struct FooImpl : Foo
{
   int bar()
   {
       return 42;
   }
}

int acceptsFoo(Foo f)
{
   return f.bar();
}

FooImpl fooImpl;
acceptsFoo(fooImpl); // yes!

They'll get an implicit vtbl ptr, and since they don't support struct 
inheritance, no slicing is possible.

Reply via email to