Jump to page: 1 2
Thread overview
Build interface from abstract class
May 28, 2018
DigitalDesigns
May 28, 2018
Simen Kjærås
May 28, 2018
DigitalDesigns
May 28, 2018
arturg
May 29, 2018
DigitalDesigns
May 29, 2018
arturg
May 29, 2018
DigitalDesigns
May 30, 2018
DigitalDesigns
May 30, 2018
Chameleon
May 30, 2018
DigitalDesigns
May 30, 2018
Simen Kjærås
May 30, 2018
DigitalDesigns
May 28, 2018
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
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
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
this might help you,
https://dpaste.dzfl.pl/2cf844a11e3f

you can use them to generate the functions as strings.
May 29, 2018
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
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
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 30, 2018
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 30, 2018
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 30, 2018
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.
« First   ‹ Prev
1 2