digitalmars.D.learn - Is there a way to forward-declare interfaces to avoid module interdependencies?
- =?ISO-8859-1?Q?Per_=C5ngstr=F6m?= (111/111) Nov 11 2010 I hope we can all agree that minimizing module interdependencies is a
- Jacob Carlborg (12/121) Nov 11 2010 First you have to import ITeacher in Student.d (if I'm reading this
- =?ISO-8859-1?Q?Per_=C5ngstr=F6m?= (23/33) Nov 11 2010 No, the code works fine as is, but if I move the import of ITeacher from...
- Steven Schveighoffer (21/47) Nov 11 2010 First, you can't forward-declare classes in one file that are defined in...
- =?UTF-8?B?UGVyIMOFbmdzdHLDtm0=?= (14/31) Nov 14 2010 I get it now. So there is no way out for me: I have to import any module...
- Steven Schveighoffer (8/41) Nov 15 2010 I said that wrong, I meant forward *references* not forward
- =?UTF-8?B?UGVyIMOFbmdzdHLDtm0=?= (32/40) Nov 28 2010 I have found a way to detect unwanted interdependencies between
- Steven Schveighoffer (6/45) Nov 29 2010 It could only be detected at link-time, and the linker doesn't do any
- =?UTF-8?B?UGVyIMOFbmdzdHLDtm0=?= (12/18) Nov 30 2010 Actually, I should have written "without running the program", be it
- bearophile (4/6) Nov 11 2010 To me the D module system seems closer to the Python one than to the Jav...
- Jonathan M Davis (8/14) Nov 11 2010 It may be - I haven't really used python, so I can't say - but in compar...
I hope we can all agree that minimizing module interdependencies is a good thing, that a highly interdependent set of modules is generally harder to understand and more difficult to test than if the module-dependency chain is acyclic. With that in mind, suppose I have the following C++ code, organized with a separation of interface and implementation, with no circular include dependencies: // IStudent.h class ITeacher; // forward reference class IStudent { public: virtual void ask(ITeacher &) = 0; virtual void learn(ITeacher &, const char * knowledge) = 0; }; // ITeacher.h class IStudent; // forward reference class ITeacher { public: virtual void teach(IStudent &) = 0; }; // Student.h #include "IStudent.h" class Student : public IStudent { public: void ask(ITeacher &); void learn(ITeacher &, const char * knowledge); }; // Teacher.h #include "ITeacher.h" class Teacher: public ITeacher { public: void teach(IStudent &); }; // Student.cpp #include "Student.h" #include "ITeacher.h" void Student::ask(ITeacher & teacher) { teacher.teach(*this); } void Student::learn(ITeacher &, const char * ) { } // Teacher.cpp #include "Teacher.h" #include "IStudent.h" void Teacher::teach(IStudent & student) { student.learn(*this, "knowledge"); } // main.cpp #include "Student.h" #include "Teacher.h" int main() { Student student; Teacher teacher; student.ask(teacher); return 0; } Below is my attempt at porting the code to D2. I hope I'm missing something due to my limited experience with D, but it seems D forces me to create circular dependencies between modules ITeacher and IStudent, since I cannot find a way to forward-declare types external to the current module in D like in C/C++. // IStudent.d import ITeacher; // would like to forward-declare the interface instead interface IStudent { void ask(ITeacher); void learn(ITeacher, string knowledge); } // ITeacher.d import IStudent; interface ITeacher { void teach(IStudent); } // Student.d import IStudent; class Student : IStudent { void ask(ITeacher teacher) { teacher.teach(this); } void learn(ITeacher teacher, string knowledge) { } } // Teacher.d import ITeacher; import IStudent; class Teacher: ITeacher { void teach(IStudent student) { student.learn(this, "knowledge"); } } // main.d import Student; import Teacher; void main() { auto student = new Student; auto teacher = new Teacher; student.ask(teacher); } So my question is: Am I missing something, or is this the D way to do it? Cheers, -- Per Å.
Nov 11 2010
On 2010-11-11 13:10, Per Ångström wrote:I hope we can all agree that minimizing module interdependencies is a good thing, that a highly interdependent set of modules is generally harder to understand and more difficult to test than if the module-dependency chain is acyclic. With that in mind, suppose I have the following C++ code, organized with a separation of interface and implementation, with no circular include dependencies: // IStudent.h class ITeacher; // forward reference class IStudent { public: virtual void ask(ITeacher &) = 0; virtual void learn(ITeacher &, const char * knowledge) = 0; }; // ITeacher.h class IStudent; // forward reference class ITeacher { public: virtual void teach(IStudent &) = 0; }; // Student.h #include "IStudent.h" class Student : public IStudent { public: void ask(ITeacher &); void learn(ITeacher &, const char * knowledge); }; // Teacher.h #include "ITeacher.h" class Teacher: public ITeacher { public: void teach(IStudent &); }; // Student.cpp #include "Student.h" #include "ITeacher.h" void Student::ask(ITeacher & teacher) { teacher.teach(*this); } void Student::learn(ITeacher &, const char * ) { } // Teacher.cpp #include "Teacher.h" #include "IStudent.h" void Teacher::teach(IStudent & student) { student.learn(*this, "knowledge"); } // main.cpp #include "Student.h" #include "Teacher.h" int main() { Student student; Teacher teacher; student.ask(teacher); return 0; } Below is my attempt at porting the code to D2. I hope I'm missing something due to my limited experience with D, but it seems D forces me to create circular dependencies between modules ITeacher and IStudent, since I cannot find a way to forward-declare types external to the current module in D like in C/C++. // IStudent.d import ITeacher; // would like to forward-declare the interface instead interface IStudent { void ask(ITeacher); void learn(ITeacher, string knowledge); } // ITeacher.d import IStudent; interface ITeacher { void teach(IStudent); } // Student.d import IStudent; class Student : IStudent { void ask(ITeacher teacher) { teacher.teach(this); } void learn(ITeacher teacher, string knowledge) { } } // Teacher.d import ITeacher; import IStudent; class Teacher: ITeacher { void teach(IStudent student) { student.learn(this, "knowledge"); } } // main.d import Student; import Teacher; void main() { auto student = new Student; auto teacher = new Teacher; student.ask(teacher); } So my question is: Am I missing something, or is this the D way to do it? Cheers,First you have to import ITeacher in Student.d (if I'm reading this right). Second, D has (generally) no problems with circular references. You only get problems when two modules is a part of a circular reference and both have module constructors. If you porting C++ code you will not have this problem since C++ doesn't have module constructors. BTW, you generally don't separate your interface and implementation in D (you can to that if you want to hide your implementation). The import/module system in D is more like the one in Java than the one in C/C++. -- /Jacob Carlborg
Nov 11 2010
On 2010-11-11 15:51, Jacob Carlborg wrote:First you have to import ITeacher in Student.d (if I'm reading this right).No, the code works fine as is, but if I move the import of ITeacher from IStudent.d to Student.d, IStudent.d won't compile: IStudent.d(4): Error: identifier 'ITeacher' is not defined IStudent.d(4): Error: ITeacher is used as a typeSecond, D has (generally) no problems with circular references.No, but I have! ;-) To be honest, I would have wanted D to outright outlaw circular references.You only get problems when two modules is a part of a circular reference and both have module constructors. If you porting C++ code you will not have this problem since C++ doesn't have module constructors.Actually, I'm more thinking whether D would be a good language for building really big systems. If the language more or less requires many modules to import each other, that's a big drawback in my opinion, since that makes a mess of the dependency graph. It also makes independent testing of individual modules a lot harder.BTW, you generally don't separate your interface and implementation in D (you can to that if you want to hide your implementation).I think information hiding is essential for a large-scale design to be comprehensible as a whole. Agreed, the module system in D hides a lot of implementation detail from being included in each compilation unit, but we must also consider not overloading the programmer with needless information. But then again, maybe I just need to stop thinking in C++.The import/module system in D is more like the one in Java than the one in C/C++.OK, I'm more familiar with C++. But I really like the concept of importing modules instead of file inclusion. Cheers, -- Per Å.
Nov 11 2010
On Thu, 11 Nov 2010 11:07:42 -0500, Per Ångström <d-news autark.se> wrote:On 2010-11-11 15:51, Jacob Carlborg wrote:First, you can't forward-declare classes in one file that are defined in another file, instead of importing. The reason is because in D, the module is the namespace that the class is declared in. So for instance, when you define IStudent in IStudent.d, it's full name is IStudent.Istudent if you did something like: interface IStudent; in ITeacher.d, then it's full name would be ITeacher.IStudent, not IStudent.IStudent. So it's not the same. Second, you are only having fits about forward declarations because you aren't used to it :) That being said, D has had many forward reference bugs (I believe there are still a few active ones), so it doesn't always *just work*. Plus there's the issue of circular imports causing code to fail at runtime due to module construction dependencies. But in any case, you shouldn't worry about it, just import away, and the compiler can take it.First you have to import ITeacher in Student.d (if I'm reading this right).No, the code works fine as is, but if I move the import of ITeacher from IStudent.d to Student.d, IStudent.d won't compile: IStudent.d(4): Error: identifier 'ITeacher' is not defined IStudent.d(4): Error: ITeacher is used as a typeSecond, D has (generally) no problems with circular references.No, but I have! ;-) To be honest, I would have wanted D to outright outlaw circular references.um... Java anyone? I think there's been one or two big systems built with Java, but I might be guessing :)You only get problems when two modules is a part of a circular reference and both have module constructors. If you porting C++ code you will not have this problem since C++ doesn't have module constructors.Actually, I'm more thinking whether D would be a good language for building really big systems. If the language more or less requires many modules to import each other, that's a big drawback in my opinion, since that makes a mess of the dependency graph. It also makes independent testing of individual modules a lot harder.Yes :) -SteveBTW, you generally don't separate your interface and implementation in D (you can to that if you want to hide your implementation).I think information hiding is essential for a large-scale design to be comprehensible as a whole. Agreed, the module system in D hides a lot of implementation detail from being included in each compilation unit, but we must also consider not overloading the programmer with needless information. But then again, maybe I just need to stop thinking in C++.
Nov 11 2010
On 2010-11-11 17:21, Steven Schveighoffer wrote:First, you can't forward-declare classes in one file that are defined in another file, instead of importing. The reason is because in D, the module is the namespace that the class is declared in. So for instance, when you define IStudent in IStudent.d, it's full name is IStudent.Istudent if you did something like: interface IStudent; in ITeacher.d, then it's full name would be ITeacher.IStudent, not IStudent.IStudent. So it's not the same.I get it now. So there is no way out for me: I have to import any module whose interfaces I use, even though I'm only referring to their names.Second, you are only having fits about forward declarations because you aren't used to it :)Actually, I would say it's the other way around: I am used to them (being able to refer to an unknown identifier as long as I don't use it) in C and C++ and find it lacking in D.That being said, D has had many forward reference bugs (I believe there are still a few active ones), so it doesn't always *just work*. Plus there's the issue of circular imports causing code to fail at runtime due to module construction dependencies. But in any case, you shouldn't worry about it, just import away, and the compiler can take it.But I still think cutting down on intermodule dependencies is a good thing, for ease of comprehension and unit testing. Fortunately, by separating interface and implementation, one can at least keep the implementation modules free from interdependencies. It's not perfect but it's not a complete mess either. Cheers, -- Per Ã….
Nov 14 2010
On Sun, 14 Nov 2010 05:28:29 -0500, Per Ångström <d-news autark.se> wrote:On 2010-11-11 17:21, Steven Schveighoffer wrote:I said that wrong, I meant forward *references* not forward *declrations*. That is, referring to things that aren't declared yet.First, you can't forward-declare classes in one file that are defined in another file, instead of importing. The reason is because in D, the module is the namespace that the class is declared in. So for instance, when you define IStudent in IStudent.d, it's full name is IStudent.Istudent if you did something like: interface IStudent; in ITeacher.d, then it's full name would be ITeacher.IStudent, not IStudent.IStudent. So it's not the same.I get it now. So there is no way out for me: I have to import any module whose interfaces I use, even though I'm only referring to their names.Second, you are only having fits about forward declarations because you aren't used to it :)Actually, I would say it's the other way around: I am used to them (being able to refer to an unknown identifier as long as I don't use it) in C and C++ and find it lacking in D.> That being said, D has had many forward referenceIt is one way to keep dependencies down. And less dependencies means less coupling, meaning importing one module doesn't make you import (and include the code for) many other modules. The compiler isn't yet mature enough to trim out some code that is never used. -Stevebugs (I believe there are still a few active ones), so it doesn't always *just work*. Plus there's the issue of circular imports causing code to fail at runtime due to module construction dependencies. But in any case, you shouldn't worry about it, just import away, and the compiler can take it.But I still think cutting down on intermodule dependencies is a good thing, for ease of comprehension and unit testing. Fortunately, by separating interface and implementation, one can at least keep the implementation modules free from interdependencies. It's not perfect but it's not a complete mess either.
Nov 15 2010
On 2010-11-15 16:52, Steven Schveighoffer wrote:On Sun, 14 Nov 2010 05:28:29 -0500, Per Ångström <d-news autark.se> wrote:I have found a way to detect unwanted interdependencies between implementation modules, by declaring a static constructor in those modules that should not depend on other implementation modules. // Student.d import IStudent; import ITeacher; class Student : IStudent { // ... void ask(ITeacher teacher) { teacher.teach(this); } static this() {} } // Teacher.d import ITeacher; import IStudent; class Teacher: ITeacher { void teach(IStudent student) { student.learn(this, "knowledge"); } static this() {} } Now if Teacher and Student were to accidentally import each other, I would get a runtime error: object.Exception: Cyclic dependency in module Student However, I wish the situation could be detected at compile-time. -- Cheers, Per Å.Fortunately, by separating interface and implementation, one can at least keep the implementation modules free from interdependencies. It's not perfect but it's not a complete mess either.It is one way to keep dependencies down. And less dependencies means less coupling, meaning importing one module doesn't make you import (and include the code for) many other modules. The compiler isn't yet mature enough to trim out some code that is never used.
Nov 28 2010
On Sun, 28 Nov 2010 09:58:42 -0500, Per Ångström <d-news autark.se> wrote:On 2010-11-15 16:52, Steven Schveighoffer wrote:It could only be detected at link-time, and the linker doesn't do any custom processing, it just links. But in any case, I still don't understand why you want to avoid interdependencies. D is supposed to handle them well. -SteveOn Sun, 14 Nov 2010 05:28:29 -0500, Per Ångström <d-news autark.se> wrote:I have found a way to detect unwanted interdependencies between implementation modules, by declaring a static constructor in those modules that should not depend on other implementation modules. // Student.d import IStudent; import ITeacher; class Student : IStudent { // ... void ask(ITeacher teacher) { teacher.teach(this); } static this() {} } // Teacher.d import ITeacher; import IStudent; class Teacher: ITeacher { void teach(IStudent student) { student.learn(this, "knowledge"); } static this() {} } Now if Teacher and Student were to accidentally import each other, I would get a runtime error: object.Exception: Cyclic dependency in module Student However, I wish the situation could be detected at compile-time.Fortunately, by separating interface and implementation, one can at least keep the implementation modules free from interdependencies. It's not perfect but it's not a complete mess either.It is one way to keep dependencies down. And less dependencies means less coupling, meaning importing one module doesn't make you import (and include the code for) many other modules. The compiler isn't yet mature enough to trim out some code that is never used.
Nov 29 2010
On 2010-11-29 16:39, Steven Schveighoffer wrote:On Sun, 28 Nov 2010 09:58:42 -0500, Per Ångström <d-news autark.se> wrote:Actually, I should have written "without running the program", be it compile-time or link-time.However, I wish the situation could be detected at compile-time.It could only be detected at link-time, and the linker doesn't do any custom processing, it just links.But in any case, I still don't understand why you want to avoid interdependencies. D is supposed to handle them well.It's something I caught from reading Lakos's _Large Scale C++ Software Design_ back in the 90's. Of course, D's module system alleviates a lot of the problems with interdependencies in C++, but still, highly interdependent systems are harder to test and less easy to change. I don't think D has changed the game completely, making interdependencies largely a non-issue. -- Cheers, Per Å.
Nov 30 2010
Jacob Carlborg:The import/module system in D is more like the one in Java than the one in C/C++.To me the D module system seems closer to the Python one than to the Java one. Bye, bearophile
Nov 11 2010
On Thursday, November 11, 2010 09:42:02 bearophile wrote:Jacob Carlborg:It may be - I haven't really used python, so I can't say - but in comparison to C++, the D and Java module systems are almost identical. Sure, there _are_ differences (like how D's modules don't have to have exactly one public class declaration which has the same name as the module), but they're very similar. Python is probably very similar as well but with fewer differences. Regardless however, in comparison to C++, Java's module system is very close to D's. - Jonathan M DavisThe import/module system in D is more like the one in Java than the one in C/C++.To me the D module system seems closer to the Python one than to the Java one.
Nov 11 2010