www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - "static interface" for structs

reply "Marco Leise" <Marco.Leise gmx.de> writes:
First things first: I've been told on IRC that this idea has already come  
up and was rejected in favor of template guards. But it wasn't clear what  
the reasons were.

The idea is to have "static interface" or "protocol" for structs similar  
to the "interface" for classes. The benefit comes from the fact that in D  
we often see structs used in a way other languages can't. A prime example  
are function templates for sorting algorithms that can take any kind of  
range (built-in, class, struct). But with this broader use, comes the need  
to be explicit about the interface of a struct akin to interfaces for  
classes. A compiler or IDE can detect missing or incorrectly spelled  
methods on structs that implement a protocol. Readers of your code can  
understand from the declaration that a struct is forward-iterable or  
supports some other protocol.

These are the rules:
1. Protocols share the common symbol space with classes, interfaces, etc.
2. They can be (multiple-)inherited by interfaces and other protocols.
3. They can be implemented by structs as well as classes.

I use "protocol" as a keyword to avoid any confusion with what "interface"  
allows you to do at runtime. Also this is not "C++ concepts" in disguise.  
Actually I didn't know about until now. It is somewhat of a really small  
subset of that though.


protocol InputRange(E) {
	 property bool empty;
	 property E front;
	E popFront();
}

// defines a property that returns the declared range type
protocol ForwardRange(E) : InputRange!E {
	 property typeof(this) save;
}

// just a tag
protocol InfiniteRange(E) : ForwardRange!E {}


I used the range module as an example, just because it is the first that  
came to my head. Actually on browsing the source for a while I realized  
that checks like "hasLength", which look like a candidate for a protocol  
are more flexible than a method/field declaration. length can be a field  
or property and return any integral type. So on 32-bit systems it works  
with 64-bit file sizes and 32-bit indexes on arrays just as well. So even  
if protocols make for a nice syntax, they don't fit the bill. Other ideas  
for struct "interfaces"?
Oct 11 2011
next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
Actually, the __traits(compiles, ...) is a marvelous and very powerful
tool that will allow you to test if the struct is fit for your
particular task by just specifying the task.

On Tue, Oct 11, 2011 at 4:30 PM, Marco Leise <Marco.Leise gmx.de> wrote:
 First things first: I've been told on IRC that this idea has already come=

 and was rejected in favor of template guards. But it wasn't clear what th=

 reasons were.

 The idea is to have "static interface" or "protocol" for structs similar =

 the "interface" for classes. The benefit comes from the fact that in D we
 often see structs used in a way other languages can't. A prime example ar=

 function templates for sorting algorithms that can take any kind of range
 (built-in, class, struct). But with this broader use, comes the need to b=

 explicit about the interface of a struct akin to interfaces for classes. =

 compiler or IDE can detect missing or incorrectly spelled methods on stru=

 that implement a protocol. Readers of your code can understand from the
 declaration that a struct is forward-iterable or supports some other
 protocol.

 These are the rules:
 1. Protocols share the common symbol space with classes, interfaces, etc.
 2. They can be (multiple-)inherited by interfaces and other protocols.
 3. They can be implemented by structs as well as classes.

 I use "protocol" as a keyword to avoid any confusion with what "interface=

 allows you to do at runtime. Also this is not "C++ concepts" in disguise.
 Actually I didn't know about until now. It is somewhat of a really small
 subset of that though.


 protocol InputRange(E) {
 =A0 =A0 =A0 =A0 property bool empty;
 =A0 =A0 =A0 =A0 property E front;
 =A0 =A0 =A0 =A0E popFront();
 }

 // defines a property that returns the declared range type
 protocol ForwardRange(E) : InputRange!E {
 =A0 =A0 =A0 =A0 property typeof(this) save;
 }

 // just a tag
 protocol InfiniteRange(E) : ForwardRange!E {}


 I used the range module as an example, just because it is the first that
 came to my head. Actually on browsing the source for a while I realized t=

 checks like "hasLength", which look like a candidate for a protocol are m=

 flexible than a method/field declaration. length can be a field or proper=

 and return any integral type. So on 32-bit systems it works with 64-bit f=

 sizes and 32-bit indexes on arrays just as well. So even if protocols mak=

 for a nice syntax, they don't fit the bill. Other ideas for struct
 "interfaces"?

Oct 11 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 10/11/11, Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> wrote:
 Actually, the __traits(compiles, ...) is a marvelous and very powerful
 tool that will allow you to test if the struct is fit for your
 particular task by just specifying the task.

Yeah, but you have to write very specific code to test if it really is a compatible type. And even after all that hard work you could still easily pass a type that "walks like a duck" but that you know is actually incompatible. So then you have to write specific if(!is(T == IncompatibleType)), and this doesn't scale too well. This is why I resort to having an enum boolean as the first field of a struct that defines its "protocol", but I really see that as a poor-man's implementation of interfaces.
Oct 11 2011
prev sibling next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
Right. It is far too verbose, but those protocols are too restrictive.
With no run-time value, the protocols must be very flexible to contain
any kind of requirements on structs, that are practically useful to
have.
The __traits(compiles, ...) would be awesome to be automatically be
used in templates:

void func(Type)(Type param)
{
    Type temp = new Type;
    temp.method(param);
}

This code requires the Type to be either a class or a struct with a
this(typeof(this)*) { } implemented in it.
If the test for it was automatically included in the template's
constraint, it would make templates so much easier to use.
Templates would then overload on their bodies. You'd just write
several implementations of the template and the correct one would be
automatically chosen, based on the implementation itself.
And in case none of them are applicable, the compiler would be able to
tell you exactly what does your template parameter lack.
But we don't have that, unfortunately.

On Tue, Oct 11, 2011 at 6:01 PM, Andrej Mitrovic
<andrej.mitrovich gmail.com> wrote:
 On 10/11/11, Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> wrote:
 Actually, the __traits(compiles, ...) is a marvelous and very powerful
 tool that will allow you to test if the struct is fit for your
 particular task by just specifying the task.

Yeah, but you have to write very specific code to test if it really is a compatible type. And even after all that hard work you could still easily pass a type that "walks like a duck" but that you know is actually incompatible. So then you have to write specific if(!is(T == IncompatibleType)), and this doesn't scale too well. This is why I resort to having an enum boolean as the first field of a struct that defines its "protocol", but I really see that as a poor-man's implementation of interfaces.

Oct 11 2011
prev sibling next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
BTW, you can make a template, which takes an interface type and a
struct type and evaluates to a bool, indicating whether the struct
theoretically implements the interface.
I actually had a success in doing something similar.
Here are the main features I use in these situations:
__traits(allMembers, Type);
is(typeof(member))
__traits(getOverloads, Type);

On Tue, Oct 11, 2011 at 6:01 PM, Andrej Mitrovic
<andrej.mitrovich gmail.com> wrote:
 On 10/11/11, Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> wrote:
 Actually, the __traits(compiles, ...) is a marvelous and very powerful
 tool that will allow you to test if the struct is fit for your
 particular task by just specifying the task.

Yeah, but you have to write very specific code to test if it really is a compatible type. And even after all that hard work you could still easily pass a type that "walks like a duck" but that you know is actually incompatible. So then you have to write specific if(!is(T == IncompatibleType)), and this doesn't scale too well. This is why I resort to having an enum boolean as the first field of a struct that defines its "protocol", but I really see that as a poor-man's implementation of interfaces.

Oct 11 2011
prev sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
You can have those protocols: import("my_protocol.pr");
mixin(parseProtocol(protocol)), etc...

On Tue, Oct 11, 2011 at 6:24 PM, Gor Gyolchanyan
<gor.f.gyolchanyan gmail.com> wrote:
 BTW, you can make a template, which takes an interface type and a
 struct type and evaluates to a bool, indicating whether the struct
 theoretically implements the interface.
 I actually had a success in doing something similar.
 Here are the main features I use in these situations:
 __traits(allMembers, Type);
 is(typeof(member))
 __traits(getOverloads, Type);

 On Tue, Oct 11, 2011 at 6:01 PM, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:
 On 10/11/11, Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> wrote:
 Actually, the __traits(compiles, ...) is a marvelous and very powerful
 tool that will allow you to test if the struct is fit for your
 particular task by just specifying the task.

Yeah, but you have to write very specific code to test if it really is a compatible type. And even after all that hard work you could still easily pass a type that "walks like a duck" but that you know is actually incompatible. So then you have to write specific if(!is(T == IncompatibleType)), and this doesn't scale too well. This is why I resort to having an enum boolean as the first field of a struct that defines its "protocol", but I really see that as a poor-man's implementation of interfaces.


Oct 11 2011