www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Build interface from abstract class

reply DigitalDesigns <DigitalDesigns gmail.com> writes:
Implementing interfaces can be a pain but are necessary.

I like to use abstract classes and provide a base implementation. 
It would be cool if I could use D's awesome meta features to 
extract the interface from the abstract class then build it. This 
requires some funky stuff which I'm not sure D can do

mixin(InterfaceFromAbstractClass!(MyAbstraction, "MyInterface"));

interface MyInterface2 : MyInterface
{
     final static void baz() { }
}

abstract class MyAbstraction : MyInterface2
{
      InterfaceMembers
     {
         void foo() { };
     }
}

InterfaceFromAbstractClass will get all the  InterfaceMembers 
members and declare them in an interface MyInterface.

This avoids all the duplicate code that interfaces has to create.

Is this possible in D?
May 28 2018
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 28 May 2018 at 09:59:50 UTC, DigitalDesigns wrote:
 Implementing interfaces can be a pain but are necessary.

 I like to use abstract classes and provide a base 
 implementation. It would be cool if I could use D's awesome 
 meta features to extract the interface from the abstract class 
 then build it. This requires some funky stuff which I'm not 
 sure D can do
Creating an interface based on a class is no big deal in D: interface Interface(T) { import std.traits; static foreach (fn; __traits(allMembers, T)) static foreach (overload; MemberFunctionsTuple!(T, fn)) mixin("ReturnType!overload "~fn~"(Parameters!overload);"); } class A { void fun() {} } alias IA = Interface!A; However, you run into a problem when A is expected to implement IA: in order to figure out which functions should be in the interface, we need to know which functions are in A. And in order to figure out which functions are in A, we need to know which functions are in IA. It's a loop with no real solution. In this specific case, one could consider only members of A, ignoring any interfaces or base classes. This isn't currently possible, but it's not an entirely unreasonable enhancement request. -- Simen
May 28 2018
parent reply DigitalDesigns <DigitalDesigns gmail.com> writes:
On Monday, 28 May 2018 at 11:51:20 UTC, Simen Kjærås wrote:
 On Monday, 28 May 2018 at 09:59:50 UTC, DigitalDesigns wrote:
 Implementing interfaces can be a pain but are necessary.

 I like to use abstract classes and provide a base 
 implementation. It would be cool if I could use D's awesome 
 meta features to extract the interface from the abstract class 
 then build it. This requires some funky stuff which I'm not 
 sure D can do
Creating an interface based on a class is no big deal in D: interface Interface(T) { import std.traits; static foreach (fn; __traits(allMembers, T)) static foreach (overload; MemberFunctionsTuple!(T, fn)) mixin("ReturnType!overload "~fn~"(Parameters!overload);"); } class A { void fun() {} } alias IA = Interface!A; However, you run into a problem when A is expected to implement IA: in order to figure out which functions should be in the interface, we need to know which functions are in A. And in order to figure out which functions are in A, we need to know which functions are in IA. It's a loop with no real solution. In this specific case, one could consider only members of A, ignoring any interfaces or base classes. This isn't currently possible, but it's not an entirely unreasonable enhancement request. -- Simen
I do not think this is a problem in D. Infinite recursion can always be terminated with appropriate means. 1. Use attributes. methods in class A should be marked as being for the interface. When added to the interface they will not have the attribute so will not be picked up again. 2. Your method of function creation does not pick up attributes and other factors so it is weak. Case 1 should be easy to deal with. Case 2, I am not so sure how to build the signature exactly as it is expressed. This is, of course, just a copy an paste mechanism but it would require D to provide us the signature in some way. Maybe an easy and direct way would be to pass __FILE__ and __LINE__ and extract the line? This seems a bit risky and slow. Case 1 Will not work if UDA's are required to be exactly the same between interface and class. I do not know if D enforces that but if it does it seems a bit overkill. Here is my solution that does not solve problem 2: import std.stdio; template InterfaceFromClass(alias T, string id) { auto InterfaceFromClass() { string s; import std.traits; foreach (member; __traits(allMembers, T)) { static foreach (overload; MemberFunctionsTuple!(T, member)) { enum attr = __traits(getAttributes, overload); static if (__traits(getProtection, __traits(getMember, T, member)) == "public") { mixin(`enum attrs = __traits(getAttributes, T.` ~ member ~ `);`); foreach(a; attrs) static if (is(typeof(a) == string) && a.length > 0 && a == "InterfaceMembers") { s ~= (ReturnType!overload).stringof ~" "~member~""~(Parameters!overload).stringof~";"; } } } } return "interface "~id~"\n{\n "~s~"\n}"; } } Which you can see it at work here: https://dpaste.dzfl.pl/f49be5dd7daa
May 28 2018
next sibling parent reply arturg <var.spool.mail700 gmail.com> writes:
this might help you,
https://dpaste.dzfl.pl/2cf844a11e3f

you can use them to generate the functions as strings.
May 28 2018
parent reply DigitalDesigns <DigitalDesigns gmail.com> writes:
On Monday, 28 May 2018 at 22:15:40 UTC, arturg wrote:
 this might help you,
 https://dpaste.dzfl.pl/2cf844a11e3f

 you can use them to generate the functions as strings.
Thanks, So, the problem I'm having is that I cannot use the generated interface for the abstract class because the abstract class needs the interface defined. I need to be able to forward define the interface then extend it. D doesn't like this main.d(10): Error: interface `main.B` base `A` is forward referenced interface A; mixin(Generate!(B,A)); interface B : A { } abstract class C : B { } I could see that D wants A so it can have the complete picture for B, but this is one of those problems where it shouldn't matter. This requires hoops and ultimately does not solve the problem. Any time A is used it will be treated as undefined by the compiler and throw an error. For example: pragma(msg, InterfaceFromClass!(C, "A")); mixin(InterfaceFromClass!(C, "A")); interface B : A { } abstract class C { ("InterfaceMembers") { int xe = 3; (3) void foo() { } property int x() { return 3; }; B bar() { return null; } } } abstract class D : C, B { } fails because C.bar returns a B, which has not yet fully been defined because A has not yet fully been defined. Now, if I could just get the function signature without using the type system, this wouldn't be a problem. I don't really care if B is defined yet, I just need to know it's name. I guess D tries to enforce consistency at all steps, which is a problem here because C uses a yet to be defined type, even though it will be defined soon enough without problems. One of the main culprits is isCallable which is what errors out because of the yet to be defined B. So, I guess some other method will have to work.
May 29 2018
parent reply arturg <var.spool.mail700 gmail.com> writes:
On Tuesday, 29 May 2018 at 19:06:24 UTC, DigitalDesigns wrote:
 On Monday, 28 May 2018 at 22:15:40 UTC, arturg wrote:
 this might help you,
 https://dpaste.dzfl.pl/2cf844a11e3f

 you can use them to generate the functions as strings.
Thanks, So, the problem I'm having is that I cannot use the generated interface for the abstract class because the abstract class needs the interface defined. I need to be able to forward define the interface then extend it. D doesn't like this main.d(10): Error: interface `main.B` base `A` is forward referenced interface A; mixin(Generate!(B,A)); interface B : A { } abstract class C : B { }
would it work if you define the interface but mixin the members? interface A { mixin(InterfaceFromClass!C); }
May 29 2018
parent reply DigitalDesigns <DigitalDesigns gmail.com> writes:
On Tuesday, 29 May 2018 at 20:26:52 UTC, arturg wrote:
 On Tuesday, 29 May 2018 at 19:06:24 UTC, DigitalDesigns wrote:
 On Monday, 28 May 2018 at 22:15:40 UTC, arturg wrote:
 this might help you,
 https://dpaste.dzfl.pl/2cf844a11e3f

 you can use them to generate the functions as strings.
Thanks, So, the problem I'm having is that I cannot use the generated interface for the abstract class because the abstract class needs the interface defined. I need to be able to forward define the interface then extend it. D doesn't like this main.d(10): Error: interface `main.B` base `A` is forward referenced interface A; mixin(Generate!(B,A)); interface B : A { } abstract class C : B { }
would it work if you define the interface but mixin the members? interface A { mixin(InterfaceFromClass!C); }
Yes, I made a post about it but it didn't get through or I forgot to send ;/ Doing it this way solves the original problems but creates a new problem in that any class that inherits from an abstract class that implements A doesn't work. interface A { mixin(InterfaceFromClass!C); } abstract class B : A { A foo() { return this; } int bar() { return 3; } } class C : B { // compiler says foo, bar not implemented } so, instead interface A { mixin(InterfaceFromClass!C); } abstract class B { A foo() { return this; } // requires cast int bar() { return 3; } } class C : B, A { } works but requires casting this in B which happens to work in C. So It is a fessible solution but I'm not sure why the compiler things the first case doesn't implement the methods when it clearly does. I think it looks at A and doesn't compute the mixin first when parsing C because if I put the output of the mixin directly it works. Probably a bug...
May 29 2018
parent DigitalDesigns <DigitalDesigns gmail.com> writes:
On Tuesday, 29 May 2018 at 20:53:14 UTC, DigitalDesigns wrote:
 On Tuesday, 29 May 2018 at 20:26:52 UTC, arturg wrote:
 On Tuesday, 29 May 2018 at 19:06:24 UTC, DigitalDesigns wrote:
 On Monday, 28 May 2018 at 22:15:40 UTC, arturg wrote:
 this might help you,
 https://dpaste.dzfl.pl/2cf844a11e3f

 you can use them to generate the functions as strings.
Thanks, So, the problem I'm having is that I cannot use the generated interface for the abstract class because the abstract class needs the interface defined. I need to be able to forward define the interface then extend it. D doesn't like this main.d(10): Error: interface `main.B` base `A` is forward referenced interface A; mixin(Generate!(B,A)); interface B : A { } abstract class C : B { }
would it work if you define the interface but mixin the members? interface A { mixin(InterfaceFromClass!C); }
Yes, I made a post about it but it didn't get through or I forgot to send ;/ Doing it this way solves the original problems but creates a new problem in that any class that inherits from an abstract class that implements A doesn't work. interface A { mixin(InterfaceFromClass!C); } abstract class B : A { A foo() { return this; } int bar() { return 3; } } class C : B { // compiler says foo, bar not implemented } so, instead interface A { mixin(InterfaceFromClass!C); } abstract class B { A foo() { return this; } // requires cast int bar() { return 3; } } class C : B, A { } works but requires casting this in B which happens to work in C. So It is a fessible solution but I'm not sure why the compiler things the first case doesn't implement the methods when it clearly does. I think it looks at A and doesn't compute the mixin first when parsing C because if I put the output of the mixin directly it works. Probably a bug...
This method, which imports the source code and extracts the members works but is very fragile and requires using -J: // Directly copies the data to the interface with a few hacks to fix the bug from the original, it excepts a well formatted input template InterfaceFromClass(alias T) { string InterfaceFromClass(string file = __FILE_FULL_PATH__)() { string res; import std.path, std.string, std.algorithm; enum data = import(file.baseName); enum mc = "abstract class "~T.stringof~" : "; enum im = ` ("InterfaceMembers")`; int level = 0; auto baseIndentLevel = -1; int funcIndentLevel = -1; foreach(v; data.splitLines) { string l = v; auto indentLevel = l.length - l.stripLeft().length; auto ll = l.strip(); if (ll.length == 0) continue; //res ~= to!string(l.length) ~"\n" ~ l[0..min(mc.length, l.length)] ~ "\n"; if (level == 0 && ll.length >= mc.length && ll[0..min(mc.length, ll.length)] == mc) { baseIndentLevel = indentLevel; level = 1; continue; } // Finds "abstract class <T> : " if (level == 1 && ll.length >= im.length && ll[0..min(im.length, ll.length)] == im) { level = 2; continue; } // Finds " ("InterfaceMembers))" near by if (level >= 2 && l == "}") break; if (level == 2 && ll.length > 1) { if (funcIndentLevel < 0) funcIndentLevel = indentLevel; if (funcIndentLevel < indentLevel) continue; // A simple function definition(ends with a ');') if (!ll.canFind("=") && l.length > 2 && l[$-2..$] == ");") { res ~= l ~ "\n"; continue; } // ignore fields(assumes it has no {, } but ends with a ';') if (!ll.canFind("{") && l[$-1] == ';' && ll[$-2..$] != "};") continue; // ignore functions with inline blocks if (ll.canFind(") {")) { l = l[0..$ - ll.find(") {").length] ~ ")"; } // must get function signature only(ignore body) res ~= l ~ ((ll[$] != ';') ? ";" : "") ~ "\n"; } } return res; } } Why the original code is being broke by using D's traits to build the functions properly is beyond me. I'm pretty sure it is a bug given the example I gave earlier.
May 29 2018
prev sibling next sibling parent reply Chameleon <Chameleon Chameleon.com> writes:
On Monday, 28 May 2018 at 20:13:49 UTC, DigitalDesigns wrote:
 Here is my solution that does not solve problem 2:


 import std.stdio;

 [...]
this is not programming. this is witchcraft!
May 29 2018
parent DigitalDesigns <DigitalDesigns gmail.com> writes:
On Wednesday, 30 May 2018 at 01:46:30 UTC, Chameleon wrote:
 On Monday, 28 May 2018 at 20:13:49 UTC, DigitalDesigns wrote:
 Here is my solution that does not solve problem 2:


 import std.stdio;

 [...]
this is not programming. this is witchcraft!
Just call me Dandalf the D Slayer! At least I finally got it to work. It's terrible that I have to use import and read the file in directly to do this ;/ It is working though.
May 29 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 28 May 2018 at 20:13:49 UTC, DigitalDesigns wrote:
 I do not think this is a problem in D. Infinite recursion can 
 always be terminated with appropriate means.

 1. Use attributes. methods in class A should be marked as being 
 for the interface. When added to the interface they will not 
 have the attribute so will not be picked up again.

 2. Your method of function creation does not pick up attributes 
 and other factors so it is weak.

 Here is my solution that does not solve problem 2:

 [Neat stuff]
Neat, but as you say it doesn't handle attributes or UDAs. The use of stringof is also a red flag - the same name can apply to different types in different scopes, and aliases may confuse it. Here's a version that solves both of those issues: import std.array : join; import std.meta : ApplyLeft, staticMap, Filter, Alias; import std.traits; enum isPublic(alias overload) = Alias!(__traits(getProtection, overload) == "public"); interface InterfaceFromClass(T, alias filter = isPublic) { static foreach (overload; Filter!(filter, staticMap!(ApplyLeft!(MemberFunctionsTuple, T), __traits(derivedMembers, T)))) mixin(" (__traits(getAttributes, overload)) "~attributes!overload~" ReturnType!overload "~__traits(identifier, overload)~"(Parameters!overload);"); } string attributes(alias overload)() { enum attrs = Filter!(ApplyLeft!(hasFunctionAttributes, overload), "pure", "nothrow", "ref", " property", " trusted", " safe", " nogc", " system", "const", "immutable", "inout", "shared", "return", "scope"); return [attrs].join(" "); } alias A = InterfaceFromClass!C; abstract class C : A { int n; ("InterfaceMembers") { (3) ref int foo(ref int a) { return n; } (19) safe void bar() { } } } // https://issues.dlang.org/show_bug.cgi?id=18915 // class D : C {} static assert([__traits(allMembers, A)] == ["foo", "bar"]); static assert(functionAttributes!(A.foo) == functionAttributes!(C.foo)); static assert(functionAttributes!(A.bar) == functionAttributes!(C.bar)); static assert(is(Parameters!(A.foo) == Parameters!(C.foo))); static assert(is(Parameters!(A.bar) == Parameters!(C.bar))); Note the link to issue 18915 (that's your 'string mixin output works...' post). I believe if we can fix that, the above should work. -- Simen
May 30 2018
parent DigitalDesigns <DigitalDesigns gmail.com> writes:
On Wednesday, 30 May 2018 at 10:31:27 UTC, Simen Kjærås wrote:
 On Monday, 28 May 2018 at 20:13:49 UTC, DigitalDesigns wrote:
 I do not think this is a problem in D. Infinite recursion can 
 always be terminated with appropriate means.

 1. Use attributes. methods in class A should be marked as 
 being for the interface. When added to the interface they will 
 not have the attribute so will not be picked up again.

 2. Your method of function creation does not pick up 
 attributes and other factors so it is weak.

 Here is my solution that does not solve problem 2:

 [Neat stuff]
Neat, but as you say it doesn't handle attributes or UDAs. The use of stringof is also a red flag - the same name can apply to different types in different scopes, and aliases may confuse it. Here's a version that solves both of those issues: import std.array : join; import std.meta : ApplyLeft, staticMap, Filter, Alias; import std.traits; enum isPublic(alias overload) = Alias!(__traits(getProtection, overload) == "public"); interface InterfaceFromClass(T, alias filter = isPublic) { static foreach (overload; Filter!(filter, staticMap!(ApplyLeft!(MemberFunctionsTuple, T), __traits(derivedMembers, T)))) mixin(" (__traits(getAttributes, overload)) "~attributes!overload~" ReturnType!overload "~__traits(identifier, overload)~"(Parameters!overload);"); } string attributes(alias overload)() { enum attrs = Filter!(ApplyLeft!(hasFunctionAttributes, overload), "pure", "nothrow", "ref", " property", " trusted", " safe", " nogc", " system", "const", "immutable", "inout", "shared", "return", "scope"); return [attrs].join(" "); } alias A = InterfaceFromClass!C; abstract class C : A { int n; ("InterfaceMembers") { (3) ref int foo(ref int a) { return n; } (19) safe void bar() { } } } // https://issues.dlang.org/show_bug.cgi?id=18915 // class D : C {} static assert([__traits(allMembers, A)] == ["foo", "bar"]); static assert(functionAttributes!(A.foo) == functionAttributes!(C.foo)); static assert(functionAttributes!(A.bar) == functionAttributes!(C.bar)); static assert(is(Parameters!(A.foo) == Parameters!(C.foo))); static assert(is(Parameters!(A.bar) == Parameters!(C.bar))); Note the link to issue 18915 (that's your 'string mixin output works...' post). I believe if we can fix that, the above should work. -- Simen
Yeah! with a little work you can include fields in the interfacemembers because they will be ignored. I see no reason to have to declare an interface in D except in rare occasions! One should be able to abstract out an interface from a class or abstract class and use it as the base only for others to use. If a program was "sealed" there would be no real point in using interfaces(assuming it was designed to perfection) except to get around the diamond problem, which is what interfaces solve but in the process have forced us to duplicate code. The above technique alleviates that problem greatly. It's still bring the members inside the interface because it allows one to add final, static, and normal members if desired, although, this can be done by using another interface and inheriting from that.
May 30 2018