digitalmars.D.learn - Abstract functions in child classes
- Adam (32/32) Dec 01 2011 Ok, starting to feel like I'm missing something obvious...
- Regan Heath (15/16) Dec 01 2011 This:
- Adam (5/5) Dec 01 2011 I saw that a few minutes after posting as well, but if the only time
- Steven Schveighoffer (12/25) Dec 01 2011 A Child reference could be for a further derived GrandChild type that do...
- Adam (5/5) Dec 01 2011 Because the function in this case has no definition in the base
- Jacob Carlborg (5/36) Dec 01 2011 The method in the super class could provide a partial implementation
- =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= (11/43) Dec 01 2011 Child is an abstract class because it has abstract methods. One of
- Adam (10/10) Dec 01 2011 I can see the case for a grand-child, but if a class does not provide
- =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= (3/13) Dec 01 2011 Indeed. But I'm not one to argue that explicit documentation is bad.
- Jacob Carlborg (6/61) Dec 01 2011 There's also the possibility to have the declarations in one object file...
- mta`chrono (3/3) Dec 02 2011 Even if the current behavior (what Adam mentioned) is not a bug, I think
- bearophile (4/7) Dec 02 2011 If you think so, then write it in Bugzilla :-)
- Steven Schveighoffer (21/24) Dec 02 2011 No, this is not a matter of allowing an invalid situation, the OP's code...
- Adam (18/18) Dec 02 2011 To sort of put my two cents back in, and also to be one of those "D
- Steven Schveighoffer (19/37) Dec 02 2011 This appears superfluous to me. A class is abstract either because you ...
- Adam (25/25) Dec 02 2011 I'm not sure how the fact that a class can be abstract with defined
- Steven Schveighoffer (19/38) Dec 02 2011 How do you run your normal tests without instantiating? There is no nee...
- Adam (30/30) Dec 02 2011 Your presumption is that someone is required to run tests just to
- Steven Schveighoffer (22/36) Dec 02 2011 You're being a bit dramatic, no? The code didn't compile, the compiler ...
- Adam (74/94) Dec 02 2011 'abstract'
- Steven Schveighoffer (18/80) Dec 02 2011 instantiation *is* done at compile-time. The distinct difference is a
- Jonathan M Davis (44/46) Dec 02 2011 Okay. Maybe I've been skimming over this thread too much, but I don't
- Steven Schveighoffer (22/50) Dec 05 2011 If I may describe what I see as the argument for (while trying to avoid ...
- Timon Gehr (8/14) Dec 02 2011 A second possible use case:
- Adam (3/10) Dec 02 2011 Could you expand on this case a bit? I'm not sure I follow the point
- Timon Gehr (59/70) Dec 02 2011 This is an useful pattern. I don't have a very useful example at hand,
- Timon Gehr (3/75) Dec 02 2011 Oh, forgot to mention: This would not compile, if an explicit 'abstract'...
- Adam (7/7) Dec 02 2011 So this pattern allows you to provide partial implementations of an
- Timon Gehr (10/17) Dec 02 2011 You can do that, but templates provide you with a lot more power. Note
- Jonathan M Davis (9/31) Dec 02 2011 The class is abstract whether you mark it that way or not, because it ha...
- Adam (24/24) Dec 02 2011 Ok, let me give a more *specific* case of why this is a problem.
- Andrej Mitrovic (2/2) Dec 02 2011 How can you put out a library where you don't even test your classes?
- Adam (12/12) Dec 02 2011 Ok, fine, let me put it THIS way.
- Regan Heath (6/18) Dec 02 2011 No-one is saying this, this is a "strawman". What has been said, is tha...
- Adam (14/14) Dec 02 2011 I grant you that I should test it, and I never said otherwise. If
- Regan Heath (25/28) Dec 02 2011 Well, I believe I understand your argument and I admit that I did find i...
- Adam (40/61) Dec 02 2011 and I
- travert phare.normalesup.org (Christophe) (23/23) Dec 05 2011 FWIW, I agree with Addam. One of the things I like in D is that the
- Regan Heath (16/20) Dec 02 2011 So.. the user has:
- Adam (3/3) Dec 02 2011 Or I provide it as source, in which case, D *can* check it.
- Regan Heath (10/13) Dec 02 2011 True. This is a case I hadn't considered before. In this case the user...
- Regan Heath (5/7) Dec 02 2011 YES! If you're releasing a library and not doing this... bad, bad, bad ...
Ok, starting to feel like I'm missing something obvious... The abstract keyword in the language reference states: "Functions declared as abstract can still have function bodies. This is so that even though they must be overridden, they can still provide ‘base class functionality.’" So, "they must be overridden." Does the compiler do *anything* to verify this for a child class? This compiles: import std.stdio; public abstract class Parent { public void hasDefinition() { writeln("I have a definition"); } public abstract void noDefinition(); } public class Child : Parent { public void unRelated() { writeln("Unrelated"); } } void main() { Child child; } However, if I change main() to: void main() { Parent instance = new Child(); } I get "cannot create instance of abstract class Child | function noDefinition is abstract" Why is a reference / use of child in the context of a parent required just to validate that the class is a valid extension of the parent? More to the point, why does the first case even compile?
Dec 01 2011
On Thu, 01 Dec 2011 17:50:48 -0000, Adam <Adam anizi.com> wrote:Ok, starting to feel like I'm missing something obvious...This: void main() { Child child = new Child; } also produces the (expected) error. Basically dmd was letting you get away with the abstract class because you never instantiated it. Child child; is just a reference to a Child class. You could argue the compiler should error in either case, in fact, I would. But perhaps there is a good generic programming reason not to... someone more experienced might be able to shed some light on it. Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 01 2011
I saw that a few minutes after posting as well, but if the only time it's going to actually check the definition is if I instantiate it, well... that's not so good for me if I'm writing implementations to be used at a later date. Hm. Guess we'll see. :)
Dec 01 2011
On Thu, 01 Dec 2011 12:58:24 -0500, Regan Heath <regan netmail.co.nz> wrote:On Thu, 01 Dec 2011 17:50:48 -0000, Adam <Adam anizi.com> wrote:A Child reference could be for a further derived GrandChild type that does actually implement the required functions. In fact, Child is also abstract, it just isn't required to be marked as such. All marking a class as abstract does is make it uninstantiable, just like having an abstract method does. However, you can mark a class abstract to prevent it from being instantiated, even when none of its methods are abstract (could be useful in some situations). However, I have no idea why you'd mark a concrete function as abstract. That seems like a "just because we could" feature. -SteveOk, starting to feel like I'm missing something obvious...This: void main() { Child child = new Child; } also produces the (expected) error. Basically dmd was letting you get away with the abstract class because you never instantiated it. Child child; is just a reference to a Child class. You could argue the compiler should error in either case, in fact, I would. But perhaps there is a good generic programming reason not to... someone more experienced might be able to shed some light on it.
Dec 01 2011
Because the function in this case has no definition in the base abstract class (Parent), but is referenced / called by Parent's members. I did not want Parent to provide a default definition for the function precisely because a large point of the abstract was that method.
Dec 01 2011
On 2011-12-01 19:18, Steven Schveighoffer wrote:On Thu, 01 Dec 2011 12:58:24 -0500, Regan Heath <regan netmail.co.nz> wrote:The method in the super class could provide a partial implementation that sub class can call. But that might be better divided in two methods. -- /Jacob CarlborgOn Thu, 01 Dec 2011 17:50:48 -0000, Adam <Adam anizi.com> wrote:A Child reference could be for a further derived GrandChild type that does actually implement the required functions. In fact, Child is also abstract, it just isn't required to be marked as such. All marking a class as abstract does is make it uninstantiable, just like having an abstract method does. However, you can mark a class abstract to prevent it from being instantiated, even when none of its methods are abstract (could be useful in some situations). However, I have no idea why you'd mark a concrete function as abstract. That seems like a "just because we could" feature. -SteveOk, starting to feel like I'm missing something obvious...This: void main() { Child child = new Child; } also produces the (expected) error. Basically dmd was letting you get away with the abstract class because you never instantiated it. Child child; is just a reference to a Child class. You could argue the compiler should error in either case, in fact, I would. But perhaps there is a good generic programming reason not to... someone more experienced might be able to shed some light on it.
Dec 01 2011
On Thu, 01 Dec 2011 18:50:48 +0100, Adam <Adam anizi.com> wrote:Ok, starting to feel like I'm missing something obvious... The abstract keyword in the language reference states: "Functions declared as abstract can still have function bodies. This is so that even though they must be overridden, they can still provide =EF=BF=BDbase class functionality.=EF=BF=BD" So, "they must be overridden." Does the compiler do *anything* to verify this for a child class? This compiles: import std.stdio; public abstract class Parent { public void hasDefinition() { writeln("I have a definition"); } public abstract void noDefinition(); } public class Child : Parent { public void unRelated() { writeln("Unrelated"); } } void main() { Child child; } However, if I change main() to: void main() { Parent instance =3D new Child(); } I get "cannot create instance of abstract class Child | function noDefinition is abstract" Why is a reference / use of child in the context of a parent required just to validate that the class is a valid extension of the parent? More to the point, why does the first case even compile?Child is an abstract class because it has abstract methods. One of these is the original hasDefinition, the other is noDefinition. Child itself is under no obligation to override them, because there could be a class GrandChild : Child, which does override them. Declaring a variable of type Child, where Child is abstract class, should of course not be an error. That child could be either a Son or a Daughter (or a transvestite child, I guess, but let's not get too carried away), both of whom override these abstract methods. That said, it would be a lot clearer if the language gave an error when a class with abstract methods is not marked abstract.
Dec 01 2011
I can see the case for a grand-child, but if a class does not provide a definition for an abstract member, is that class not, by association, abstract? "Classes become abstract if they are defined within an abstract attribute, or if any of the virtual member functions within it are declared as abstract." I *assume* that by extending Parent, Child inherits the abstract function. If inheriting an abstract member transitively makes Child an abstract, then I find that the abstract keyword at the class level is little more than explicit documentation.
Dec 01 2011
On Thu, 01 Dec 2011 19:19:49 +0100, Adam <Adam anizi.com> wrote:I can see the case for a grand-child, but if a class does not provide a definition for an abstract member, is that class not, by association, abstract?Of course. That's what I said. Or meant, at any rate."Classes become abstract if they are defined within an abstract attribute, or if any of the virtual member functions within it are declared as abstract." I *assume* that by extending Parent, Child inherits the abstract function. If inheriting an abstract member transitively makes Child an abstract, then I find that the abstract keyword at the class level is little more than explicit documentation.Indeed. But I'm not one to argue that explicit documentation is bad.
Dec 01 2011
On 2011-12-01 19:14, Simen Kjærås wrote:On Thu, 01 Dec 2011 18:50:48 +0100, Adam <Adam anizi.com> wrote:There's also the possibility to have the declarations in one object file and the implementation in another, if I recall correctly. This allows to use a .di and .d file, similar to .c and .h in C/C++. -- /Jacob CarlborgOk, starting to feel like I'm missing something obvious... The abstract keyword in the language reference states: "Functions declared as abstract can still have function bodies. This is so that even though they must be overridden, they can still provide �base class functionality.�" So, "they must be overridden." Does the compiler do *anything* to verify this for a child class? This compiles: import std.stdio; public abstract class Parent { public void hasDefinition() { writeln("I have a definition"); } public abstract void noDefinition(); } public class Child : Parent { public void unRelated() { writeln("Unrelated"); } } void main() { Child child; } However, if I change main() to: void main() { Parent instance = new Child(); } I get "cannot create instance of abstract class Child | function noDefinition is abstract" Why is a reference / use of child in the context of a parent required just to validate that the class is a valid extension of the parent? More to the point, why does the first case even compile?Child is an abstract class because it has abstract methods. One of these is the original hasDefinition, the other is noDefinition. Child itself is under no obligation to override them, because there could be a class GrandChild : Child, which does override them. Declaring a variable of type Child, where Child is abstract class, should of course not be an error. That child could be either a Son or a Daughter (or a transvestite child, I guess, but let's not get too carried away), both of whom override these abstract methods. That said, it would be a lot clearer if the language gave an error when a class with abstract methods is not marked abstract.
Dec 01 2011
Even if the current behavior (what Adam mentioned) is not a bug, I think it seems to be a pitfall for std::programmer. The language/compiler should be more restrictive in this case.
Dec 02 2011
mta`chrono Wrote:Even if the current behavior (what Adam mentioned) is not a bug, I think it seems to be a pitfall for std::programmer. The language/compiler should be more restrictive in this case.If you think so, then write it in Bugzilla :-) Bye, bearophile
Dec 02 2011
On Fri, 02 Dec 2011 03:06:37 -0500, mta`chrono <chrono mta-international.net> wrote:Even if the current behavior (what Adam mentioned) is not a bug, I think it seems to be a pitfall for std::programmer. The language/compiler should be more restrictive in this case.No, this is not a matter of allowing an invalid situation, the OP's code is perfectly viable and legal. Here is a simpler example: abstract class Parent { abstract void foo(); } class Child : Parent { override void foo() {} } void main() { Parent parent; parent = new Child(); } why should it be disallowed to declare a variable of abstract type? You aren't instantiating it. It's the act of instantiation which is not and should not be allowed. -Steve
Dec 02 2011
To sort of put my two cents back in, and also to be one of those "D should be like Java!" advocates, the problem is largely that a class that inherits from an abstract and does *not* override some abstract member becomes implicitly (to the user) abstract. The way abstracts work in Java is that, in order to maintain that "child" is an abstract (so that the actual implementation is GrandChild), you must declare that both Child is an abstract class and redeclare the function in question. Now, perhaps there are good reasons in D for not requiring Child to be declared abstract, but I'm not sure what they are. If a class having any members that are abstract is implicitly abstract, then the programmer should probably have to declare that the class is abstract, as well. The problem I ran into is that, until instantiation, the only way I knew that Child was abstract would have been to go look at Parent and see that I had forgotten to override a method. Overall, the behavior seems "unexpected" (even if it's a personal problem).
Dec 02 2011
On Fri, 02 Dec 2011 09:54:10 -0500, Adam <Adam anizi.com> wrote:To sort of put my two cents back in, and also to be one of those "D should be like Java!" advocates, the problem is largely that a class that inherits from an abstract and does *not* override some abstract member becomes implicitly (to the user) abstract.Yes that is the state of affairs.The way abstracts work in Java is that, in order to maintain that "child" is an abstract (so that the actual implementation is GrandChild), you must declare that both Child is an abstract class and redeclare the function in question.This appears superfluous to me. A class is abstract either because you say it is, or because you haven't fully implemented all methods. Your same line of thinking is used to promote mandatory overrides, which alerts you when something that was an override stops overriding or vice versa. However, we aren't in the same place with abstract, since you *can* declare a class abstract even though all its methods are concrete.Now, perhaps there are good reasons in D for not requiring Child to be declared abstract, but I'm not sure what they are. If a class having any members that are abstract is implicitly abstract, then the programmer should probably have to declare that the class is abstract, as well. The problem I ran into is that, until instantiation, the only way I knew that Child was abstract would have been to go look at Parent and see that I had forgotten to override a method.Exactly. When you actually try to instantiate Child you find that it's abstract. It's not a silent error (and is caught at compile time). Now, I can see if you weren't expecting this, and you didn't test it, you may end up releasing code that is not what you wanted. But what are the chances someone doesn't test their code before release? In other words, we only gain from the compiler refusing to compile an abstract class not marked as 'abstract' if you don't instantiate it. How often would this happen?Overall, the behavior seems "unexpected" (even if it's a personal problem).It's unexpected, but caught at compile-time during development. The error message is clear. I see no reason to change things. -Steve
Dec 02 2011
I'm not sure how the fact that a class can be abstract with defined functions impacts the case either way. It's caught at compile time, but it requires explicitly testing that any given time is or is not instantiable - how else would you test for this particular case? It seems to be adding an additional test case wherein for every abstract or implementation of abstract (or supposed implementation of abstract, as is my case), you must test for instantiability. If you admit that the error is unexpected, but caught at compile- time and only if you actually test for the specific case of if a given implementation *is* instantiable, then you've clearly demonstrated that the lack of a mandatory re-declaration of an abstract results in an additional test case. This *appears* to be at odds with a few of the other things I've seen in D, which goes out of its way to ensure you do *not* accidentally do things. In particular, I'm thinking of concurrency, which does not even *allow* function-level synchronized declaration (though it does allow synchronized blocks within functions) - it's either at the class, or not at all. To step back a bit, what is the *benefit* of not requiring a class to be declared abstract if it does not override an abstract member? It introduces implicit behavior and the potential for an additional test case (in *what* sane world should I even HAVE to test that something is instantiable?) for the sake of not typing 8 characters in a Class definition
Dec 02 2011
On Fri, 02 Dec 2011 11:05:00 -0500, Adam <Adam anizi.com> wrote:I'm not sure how the fact that a class can be abstract with defined functions impacts the case either way. It's caught at compile time, but it requires explicitly testing that any given time is or is not instantiable - how else would you test for this particular case? It seems to be adding an additional test case wherein for every abstract or implementation of abstract (or supposed implementation of abstract, as is my case), you must test for instantiability.How do you run your normal tests without instantiating? There is no need to test instantiability, since it's implicitly tested in your unit tests.If you admit that the error is unexpected, but caught at compile- time and only if you actually test for the specific case of if a given implementation *is* instantiable, then you've clearly demonstrated that the lack of a mandatory re-declaration of an abstract results in an additional test case.No. It's covered by testing the functionality of the class. You must instantiate to test anything related to the class.To step back a bit, what is the *benefit* of not requiring a class to be declared abstract if it does not override an abstract member? It introduces implicit behavior and the potential for an additional test case (in *what* sane world should I even HAVE to test that something is instantiable?) for the sake of not typing 8 characters in a Class definitionThe benefit is, I don't have to declare something is abstract *twice*, I only have to do it by leaving the function unimplemented. This does not stop you from putting abstract on the class if you want to add that extra documentation. The doc generator probably can put whether the class is abstract directly in the docs anyway. The burden of proving benefit is on *changing* the language, not leaving it the same. What benefit is there to requiring it? You already have to instantiate to test the class, or use it, so what is the risk of not letting the compiler imply it? Contrary to something like function purity, which must be declared on the function signature for prototypes, you always know all the members of a class and its base classes. There is no hidden information, and no chance the compiler could get it wrong. -Steve
Dec 02 2011
Your presumption is that someone is required to run tests just to ensure that their class definition is what they expect. If I define a class and it's concrete (because I've defined everything), and later someone changes the parent class, my class is no longer concrete (it no longer conforms to my intention, despite having no way to actually *declare* my intentions in some explicit form). A programmer should do testing, but may not (does D require a programmer to test? No). You are implicitly testing the *definition* of a class, rather than it's *use* (or accuracy, or stability, etc). Testing or not testing is TANGENTIAL to the issue. Assume that someone is not going to do unit test declarations (and do so without running off on the wild notion that it's "bad programming," because we've already established that). WITHOUT that test, I cannot *know* that my class *IS* what I originally defined it to be. You mean you don't have to type 8 characters? I've already given you the benefit of requiring it (or in another argument, but I'm actually assuming you read the rest of this thread). I shouldn't have to instantiate to test the DEFINITION of my class. My class is concrete. Or, rather, it WAS concrete. Now it's not. I have to test this? Unittests making use of an instantiated case of my class will catch this, but what you're saying is that, in the absence of a test case instantiating my class, I have to go ahead and test the definition. In other words, I've declared this class. At the time of declaring it, it's concrete. Now I have to add a unittest to *ensure* it's concrete for an instantiation of this - and all of this ignores my previous posts and comments about USAGE of my class by others. The compiler can't get it wrong, of course (and that's a ridiculous notion, anyway), but it means that something can change implicitly and transitively.
Dec 02 2011
On Fri, 02 Dec 2011 14:06:00 -0500, Adam <Adam anizi.com> wrote:Your presumption is that someone is required to run tests just to ensure that their class definition is what they expect. If I define a class and it's concrete (because I've defined everything), and later someone changes the parent class, my class is no longer concrete (it no longer conforms to my intention, despite having no way to actually *declare* my intentions in some explicit form). A programmer should do testing, but may not (does D require a programmer to test? No). You are implicitly testing the *definition* of a class, rather than it's *use* (or accuracy, or stability, etc). Testing or not testing is TANGENTIAL to the issue. Assume that someone is not going to do unit test declarations (and do so without running off on the wild notion that it's "bad programming," because we've already established that). WITHOUT that test, I cannot *know* that my class *IS* what I originally defined it to be.You're being a bit dramatic, no? The code didn't compile, the compiler caught it, and you have invented this theoretical case (which did *not* occur) to try and make your point. I don't deny that requiring 'abstract' has some value, but what I question is how much is that value? Since a reasonable developer uses tests (or uses the code in question himself) and will end up instantiating any concrete class during testing (or usage), I'd say the value is pretty close to zero (not zero, but very close). But let's assume for a moment that it's standard practice to avoid unit testing. The error that occurs is not a sneaky silent one, it is a loud compiler error. The only risk is that the user of your library finds it before you do, because you didn't test, but it still doesn't compile. Any time an error occurs at compile time, it is a win, since the user didn't accidentally use something incorrectly. So here's the solution -- use tests. In my opinion, breaking any existing code to add this requirement is unacceptable. If the base class changes in any way, your derived class may not compile. It may break in subtle ways that *do* compile. If you are going to insist on not testing your code after the base class changes, well, then I guess you will have to get used to it failing. Just be thankful if you get a compiler error instead of a latent runtime error. -Steve
Dec 02 2011
You're being a bit dramatic, no? The code didn't compile, thecompilercaught it, and you have invented this theoretical case (which did*not*occur) to try and make your point. I don't deny that requiring'abstract'has some value, but what I question is how much is that value?Since areasonable developer uses tests (or uses the code in questionhimself) andwill end up instantiating any concrete class during testing (orusage),I'd say the value is pretty close to zero (not zero, but veryclose). A bit, but the point I've been trying to make is that it's an on- instance usage, rather than a compile-time check (Timon Gehr has since demonstrated a case as to why this could be valid), and the response I keep getting back is "you should be doing testing," when my point is that I shouldn't be having to test the concreteness / abstractness of a class. The theoretical case was to demonstrate that this could be an issue. Is the value minimal? I'd argue otherwise, but, that's probably tangential, so, moving on...But let's assume for a moment that it's standard practice to avoidunittesting. The error that occurs is not a sneaky silent one, it isa loudcompiler error. The only risk is that the user of your libraryfinds itbefore you do, because you didn't test, but it still doesn'tcompile. Anytime an error occurs at compile time, it is a win, since the userdidn'taccidentally use something incorrectly. So here's the solution --usetests. In my opinion, breaking any existing code to add thisrequirementis unacceptable.My issue here was (again, past tense because of Timon's example) that the error occurs on usage rather than compilation, even when that's quite outside of what I'd expect or want. It seemed to be at odds with a lot of other D's semantic decisions, particularly with respect to other transitive types and things liked synchronized, or even shared (which I'd probably describe as closer to infectious than transitive). What I've been asking is *why* this isn't a requirement, and except for Timon, the only answers I'd gotten were "because it would break existing code" (ok, but not an explanation as to why it was DESIGNED that way in the first place), because it was minimally inconvenient (even if more explicit, like other D parts), or that to even raise the question just suggested I was a bad programmer and that these sorts of definitions should be tested. The crux of my issue with the testing argument is that, rather than having the compiler be able to verify that a concrete class is, in fact, concrete, I'm being asked to verify via unittest or otherwise that my class is, in fact, concrete, rather than abstract. On a related note, would you argue that there is any value to be gained from *allowing* a class to be marked as concrete? That is, concrete Child : Parent {} *must* implement all abstract members? *I'd* use it, because if nothing else, it provides a guarantee to users or inheritors of my class as to its intended type (rather than using an implied definition), even if the base type changes.If the base class changes in any way, your derived class may notcompile.It may break in subtle ways that *do* compile. If you are goingto insiston not testing your code after the base class changes, well, thenI guessyou will have to get used to it failing. Just be thankful if youget acompiler error instead of a latent runtime error.And, again, my issue here is that the issue isn't caught until instantiation, and is again relying on me to test the definition of my class (rather than its usage). But, I'm going in circles, and I just kept getting pointed back to the need to test my code (which I've never contested). It just seemed particularly odd (and insulting, given the actual statements) that the implication was that I needed to test the abstractness or non-abstractness of my code. Of *course* I'm going to test instantiation if I intend to instantiate it, and of *course* I'd find it there, but my point was never to ship anything untested to anyone. I just don't like having this sort of assertion occur in a unittest rather than the actual class definition (where, again, I think it belongs). Somewhat related, but I know of companies that have separate developers provide test cases than the implementers, and it's perfectly possible (and valid) in D for the implementer to hand off a finished definition of a concrete class only to discover its problematic with respect to a unittest. Or, to reaaaally stretch pointless hypothetical arguments, in using some sort of dynamic allocation of a class (Object.factory) rather than an explicit name for my concrete class. Maybe using some sort of mechanism to auto- generate unittests for any given implementation of an abstract, rather than on an implementation-by-implementation basis. Anyway, I have my answer, and I know that D *does* have a reason for this implicitism.
Dec 02 2011
On Fri, 02 Dec 2011 16:13:45 -0500, Adam <Adam anizi.com> wrote:instantiation *is* done at compile-time. The distinct difference is a compile-time error vs. a runtime exception. In other words, it can't be used in the way you may have intended, but at least it doesn't *compile* in an invalid wayYou're being a bit dramatic, no? The code didn't compile, thecompilercaught it, and you have invented this theoretical case (which did*not*occur) to try and make your point. I don't deny that requiring'abstract'has some value, but what I question is how much is that value?Since areasonable developer uses tests (or uses the code in questionhimself) andwill end up instantiating any concrete class during testing (orusage),I'd say the value is pretty close to zero (not zero, but veryclose). A bit, but the point I've been trying to make is that it's an on- instance usage, rather than a compile-time check (Timon Gehr has since demonstrated a case as to why this could be valid), and the response I keep getting back is "you should be doing testing," when my point is that I shouldn't be having to test the concreteness / abstractness of a class. The theoretical case was to demonstrate that this could be an issue. Is the value minimal? I'd argue otherwise, but, that's probably tangential, so, moving on...Why was it designed that way in the first place? Probably because of C++ legacy and/or early decisions by Walter. This isn't a case of the current way is better than your way, it's a case of your way is only marginally better than the current way. With D2 becoming closer to final, we have to have a very high bar for breaking existing code.But let's assume for a moment that it's standard practice to avoidunittesting. The error that occurs is not a sneaky silent one, it isa loudcompiler error. The only risk is that the user of your libraryfinds itbefore you do, because you didn't test, but it still doesn'tcompile. Anytime an error occurs at compile time, it is a win, since the userdidn'taccidentally use something incorrectly. So here's the solution --usetests. In my opinion, breaking any existing code to add thisrequirementis unacceptable.My issue here was (again, past tense because of Timon's example) that the error occurs on usage rather than compilation, even when that's quite outside of what I'd expect or want. It seemed to be at odds with a lot of other D's semantic decisions, particularly with respect to other transitive types and things liked synchronized, or even shared (which I'd probably describe as closer to infectious than transitive). What I've been asking is *why* this isn't a requirement, and except for Timon, the only answers I'd gotten were "because it would break existing code" (ok, but not an explanation as to why it was DESIGNED that way in the first place), because it was minimally inconvenient (even if more explicit, like other D parts), or that to even raise the question just suggested I was a bad programmer and that these sorts of definitions should be tested.The crux of my issue with the testing argument is that, rather than having the compiler be able to verify that a concrete class is, in fact, concrete, I'm being asked to verify via unittest or otherwise that my class is, in fact, concrete, rather than abstract.The compiler does verify it's concrete on instantiation -- during compile time. Again, this comes up next to never, and when it does, it's a compiler error, not a runtime error. So no possibility of bad executable exists.On a related note, would you argue that there is any value to be gained from *allowing* a class to be marked as concrete? That is, concrete Child : Parent {} *must* implement all abstract members? *I'd* use it, because if nothing else, it provides a guarantee to users or inheritors of my class as to its intended type (rather than using an implied definition), even if the base type changes.I probably wouldn't use it, because like abstract, it seems superfluous. I'd rather make my declaration by implementing or not implementing a method. But that's just me. -Steve
Dec 02 2011
On Friday, December 02, 2011 21:13:45 Adam wrote:Anyway, I have my answer, and I know that D *does* have a reason for this implicitism.Okay. Maybe I've been skimming over this thread too much, but I don't understand what forcing the programmer to mark an abstract class as abstract would do. The compiler knows that the class is abstract regardless. It's going to generate an error when you try and instantiate that class - whether or not you mark the class as abstract or not has no impact on that as long as there are abstract functions (the sole bizareness being able to mark a class as abstract when none of its functions are abstract). It is perfectly legal and desirable to be able to have a class reference for an abstract class. e.g. abstract class C { abstract void func(); } class D : C { void func() { writeln("hello world"); } } C c = new D(); The compiler has nothing to complain about with a reference to an abstract class until you try and instatiate it. So, how does explicitly marking the class abstract help with that? I do think that it's a bit odd that D doesn't require that you mark classes as abstract if they have abstract functions and that it allows classes which don't have abstract functions to be marked abstract, but that should have no effect on whether the compiler can catch bugs related to abstract classes. The fact that the class has functions which are abstract is enough for the compiler to know that the class is abstract. new C() is what needs to be disallowed for abstract classes, and that is an instantiation. It's the instantations that need to be checked, and they _are_ checked. It is a compilation error when you try and instantiate an abstract class. The only thing that I see that marking the class abstract does is give the programmer a visual indicator that the class is abstract. I do think that that's valuable, and I'd love it if it were required when any functions in the class are abstract and disallowed when none are, but I don't see how it can have any effect on what errors you're getting. It's instatiating an abstract class which as error, not declaring one, and unless you actually instantiate it, there's no way for the compiler to catch it, because there's nothing to catch. - Jonathan M Davis
Dec 02 2011
On Fri, 02 Dec 2011 17:28:33 -0500, Jonathan M Davis <jmdavisProg gmx.com> wrote:On Friday, December 02, 2011 21:13:45 Adam wrote:If I may describe what I see as the argument for (while trying to avoid a strawman), the benefit of requiring abstract is so the compilation of the class fails, not the isntantiation of the class. It's feasible that someone could run tests that don't ever instantiate that exact class type, and then they wouldn't know it wasn't instantiable. This is similar to someone who writes a library of templated objects, but doesn't test certain instantiations of them, finding out only later that it doesn't work. I actually have been affected by this, since dcollections is all templates. For example, containers of interfaces didn't work, but I didn't know until one of dcollections' users tried to do it. Then I installed a workaround. So to summarize, it's compiler error on declaration vs. compiler error on instantiation. The question then becomes, given an expected concrete class, what is the probability that nobody tries to instantiate it during testing? The worst case scenario is that the end-user's code doesn't compile.Anyway, I have my answer, and I know that D *does* have a reason for this implicitism.Okay. Maybe I've been skimming over this thread too much, but I don't understand what forcing the programmer to mark an abstract class as abstract would do. The compiler knows that the class is abstract regardless. It's going to generate an error when you try and instantiate that class - whether or not you mark the class as abstract or not has no impact on that as long as there are abstract functions (the sole bizareness being able to mark a class as abstract when none of its functions are abstract).The only thing that I see that marking the class abstract does is give the programmer a visual indicator that the class is abstract. I do think that that's valuable, and I'd love it if it were required when any functions in the class are abstract and disallowed when none are, but I don't see how it can have any effect on what errors you're getting. It's instatiating an abstract class which as error, not declaring one, and unless you actually instantiate it, there's no way for the compiler to catch it, because there's nothing to catch.This can be fixed via ddoc (which should know the class is abstract). In fact, I think it does mark it as abstract in documentation (I know it marks interfaces as abstract unnecessarily). -Steve
Dec 05 2011
On 12/02/2011 05:05 PM, Adam wrote:To step back a bit, what is the *benefit* of not requiring a class to be declared abstract if it does not override an abstract member? It introduces implicit behavior and the potential for an additional test case (in *what* sane world should I even HAVE to test that something is instantiable?) for the sake of not typing 8 characters in a Class definitionA second possible use case: class C(T): T{ // some declarations } Now you really want that template to be instantiable with T being either an abstract or a concrete class. Anything else is bound to become extremely annoying.
Dec 02 2011
A second possible use case: class C(T): T{ // some declarations }Now you really want that template to be instantiable with T beingeitheran abstract or a concrete class. Anything else is bound to become extremely annoying.Could you expand on this case a bit? I'm not sure I follow the point one way or another.
Dec 02 2011
On 12/02/2011 08:10 PM, Adam wrote:This is an useful pattern. I don't have a very useful example at hand, but this one should do. It does similar things that can be achieved with traits in Scala for example. import std.stdio; abstract class Cell(T){ abstract void set(T value); abstract const(T) get(); private: T field; } class AddSetter(C: Cell!T,T): C{ override void set(T value){field = value;} } class AddGetter(C: Cell!T,T): C{ override const(T) get(){return field;} } class DoubleCell(C: Cell!T,T): C{ override void set(T value){super.set(2*value);} } class OneUpCell(C: Cell!T,T): C{ override void set(T value){super.set(value+1);} } class SetterLogger(C:Cell!T,T): C{ override void set(T value){ super.set(value); writeln("cell has been set to '",value,"'!"); } } class GetterLogger(C:Cell!T,T): C{ override const(T) get(){ auto value = super.get(); writeln("'",value,"' has been retrieved!"); return value; } } class ConcreteCell(T): AddGetter!(AddSetter!(Cell!T)){} class OneUpDoubleSetter(T): OneUpCell!(DoubleCell!(AddSetter!(Cell!T))){} class DoubleOneUpSetter(T): DoubleCell!(OneUpCell!(AddSetter!(Cell!T))){} void main(){ Cell!string x; x = new ConcreteCell!string; x.set("hello"); writeln(x.get()); Cell!int y; y = new SetterLogger!(ConcreteCell!int); y.set(123); // prints: "cell has been set to '123'! y = new GetterLogger!(DoubleCell!(ConcreteCell!int)); y.set(1234); y.get(); // prints "'2468' has been retrieved!" y = new AddGetter!(OneUpDoubleSetter!int); y.set(100); writeln(y.get()); // prints "202" y = new AddGetter!(DoubleOneUpSetter!int); y.set(100); writeln(y.get()); // prints "201" // ... }A second possible use case: class C(T): T{ // some declarations }Now you really want that template to be instantiable with T beingeitheran abstract or a concrete class. Anything else is bound to become extremely annoying.Could you expand on this case a bit? I'm not sure I follow the point one way or another.
Dec 02 2011
On 12/02/2011 09:05 PM, Timon Gehr wrote:On 12/02/2011 08:10 PM, Adam wrote:Oh, forgot to mention: This would not compile, if an explicit 'abstract' declaration on template class definitions was required.This is an useful pattern. I don't have a very useful example at hand, but this one should do. It does similar things that can be achieved with traits in Scala for example. import std.stdio; abstract class Cell(T){ abstract void set(T value); abstract const(T) get(); private: T field; } class AddSetter(C: Cell!T,T): C{ override void set(T value){field = value;} } class AddGetter(C: Cell!T,T): C{ override const(T) get(){return field;} } class DoubleCell(C: Cell!T,T): C{ override void set(T value){super.set(2*value);} } class OneUpCell(C: Cell!T,T): C{ override void set(T value){super.set(value+1);} } class SetterLogger(C:Cell!T,T): C{ override void set(T value){ super.set(value); writeln("cell has been set to '",value,"'!"); } } class GetterLogger(C:Cell!T,T): C{ override const(T) get(){ auto value = super.get(); writeln("'",value,"' has been retrieved!"); return value; } } class ConcreteCell(T): AddGetter!(AddSetter!(Cell!T)){} class OneUpDoubleSetter(T): OneUpCell!(DoubleCell!(AddSetter!(Cell!T))){} class DoubleOneUpSetter(T): DoubleCell!(OneUpCell!(AddSetter!(Cell!T))){} void main(){ Cell!string x; x = new ConcreteCell!string; x.set("hello"); writeln(x.get()); Cell!int y; y = new SetterLogger!(ConcreteCell!int); y.set(123); // prints: "cell has been set to '123'! y = new GetterLogger!(DoubleCell!(ConcreteCell!int)); y.set(1234); y.get(); // prints "'2468' has been retrieved!" y = new AddGetter!(OneUpDoubleSetter!int); y.set(100); writeln(y.get()); // prints "202" y = new AddGetter!(DoubleOneUpSetter!int); y.set(100); writeln(y.get()); // prints "201" // ... }A second possible use case: class C(T): T{ // some declarations }Now you really want that template to be instantiable with T beingeitheran abstract or a concrete class. Anything else is bound to become extremely annoying.Could you expand on this case a bit? I'm not sure I follow the point one way or another.
Dec 02 2011
So this pattern allows you to provide partial implementations of an abstract, and use template specialization to provide a sort of "multiple inheritance" rather than strict class definition / extension. That's important in Scala because of the lack of multiple inheritance (as I understand it). Am I understanding this correctly - that the point of this approach is to replicate composition by multiple inheritance?
Dec 02 2011
On 12/02/2011 09:27 PM, Adam wrote:So this pattern allows you to provide partial implementations of an abstract, and use template specialization to provide a sort of "multiple inheritance" rather than strict class definition / extension. That's important in Scala because of the lack of multiple inheritance (as I understand it). Am I understanding this correctly - that the point of this approach is to replicate composition by multiple inheritance?You can do that, but templates provide you with a lot more power. Note that some of my examples cannot be expressed as nicely in terms of multiple inheritance. That is because they rely on the order in which the classes are composed. This is sometimes discouraged in Scala afaik. I think, because there the type of an object does not depend on the trait mixin order. (not an issue here) Parameterizing on the base class has quite some applications, its applications in C++ even have an own wikipedia page: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
Dec 02 2011
On Friday, December 02, 2011 14:54:10 Adam wrote:To sort of put my two cents back in, and also to be one of those "D should be like Java!" advocates, the problem is largely that a class that inherits from an abstract and does *not* override some abstract member becomes implicitly (to the user) abstract. The way abstracts work in Java is that, in order to maintain that "child" is an abstract (so that the actual implementation is GrandChild), you must declare that both Child is an abstract class and redeclare the function in question. Now, perhaps there are good reasons in D for not requiring Child to be declared abstract, but I'm not sure what they are. If a class having any members that are abstract is implicitly abstract, then the programmer should probably have to declare that the class is abstract, as well. The problem I ran into is that, until instantiation, the only way I knew that Child was abstract would have been to go look at Parent and see that I had forgotten to override a method. Overall, the behavior seems "unexpected" (even if it's a personal problem).The class is abstract whether you mark it that way or not, because it has abstract methods. All marking the class as absract is going to do is give the programmer a visual clue that it's abstract. So, arguably, it really doesn't matter. Now, I personally think that it should be required if the class is abstract, since it's more consistent that way, but it's not going to have any effect on error messags are anything like that. From the compiler's perspective, there just isn't any need. - Jonathan M Davis
Dec 02 2011
Ok, let me give a more *specific* case of why this is a problem. Suppose we have our Parent and Child cases from before Parent exists in Library A Child exists in my library, B Library C, used by some developer, uses my library, B. My Child is not meant to be abstract - it's intended to me instantiable, and I document it as such. My documentation might even include examples of construction. I override all abstract methods of Parent. Users of my library use instances of Child regularly. Now, one day, the maintainer of Parent adds a new abstract function to Parent. Please don't contest that this could happen - I see it regularly in every language and every model of maintenance in real- world development. Sometimes those interface developers are just sadists. Now, my library still compiles. But, suddenly, users of my library are seeing errors on trying to instantiate. So what do I have to do to prevent this? I have to *explicitly* check that Child is or is not instantiable, probably via unittest. The alternative is that I must check over and read for every change to the Parent library, even though the only reason I need to be looking for new abstract functions is the exact same reason I'd need to explicitly check that something is instantiable.
Dec 02 2011
How can you put out a library where you don't even test your classes? That's just bad programming, period.
Dec 02 2011
Ok, fine, let me put it THIS way. Suppose I use a parent library, and *I* don't update it. The USER of my library provides an updated version for some unrelated reason. So, NOT testing that something is instantiable or not - JUST that it's instantiable - is bad programming... ...but requiring 8 characters to a class definition *is ok*? So the only way to deal with this is *discipline*? What you're telling me is that instead of requiring a class to be explicitly abstract or not, it's instead a requirement of *good programming* to test that something IS, in fact, ABSTRACT OR NOT? What?
Dec 02 2011
On Fri, 02 Dec 2011 17:24:11 -0000, Adam <Adam anizi.com> wrote:Ok, fine, let me put it THIS way. Suppose I use a parent library, and *I* don't update it. The USER of my library provides an updated version for some unrelated reason. So, NOT testing that something is instantiable or not - JUST that it's instantiable - is bad programming... ...but requiring 8 characters to a class definition *is ok*? So the only way to deal with this is *discipline*? What you're telling me is that instead of requiring a class to be explicitly abstract or not, it's instead a requirement of *good programming* to test that something IS, in fact, ABSTRACT OR NOT? What?No-one is saying this, this is a "strawman". What has been said, is that if you were to distribute a library you should test it before releasing it. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011
I grant you that I should test it, and I never said otherwise. If I'm attacking a strawman, it's the same one that was just put in front of me as a misinterpretation of my argument. What I contest and addressed was a test for the sole purpose of determining if my class, assumed to be non-abstract, was instantiable or not. Other tests being necessary for something to be good programming or not, the issue here is that a test or a manual perusal of the base class for changes (specifically, for the addition of new abstract members) is required *just* to assert that my class is or is not abstract. Yes, I should test my class, but I shouldn't have to test it just to ensure that it's the class I *intended* to create, rather than the one the compiler assumes because of a base class change.
Dec 02 2011
On Fri, 02 Dec 2011 17:43:04 -0000, Adam <Adam anizi.com> wrote:I grant you that I should test it, and I never said otherwise. If I'm attacking a strawman, it's the same one that was just put in front of me as a misinterpretation of my argument.Well, I believe I understand your argument and I admit that I did find it odd that the error was not detected until the class was instantiated. But, once I thought about it I understood why this is the case, and I believe you understand it as well. I also understand that you'd rather it wasn't the case. I think the only area of disagreement here is how much of a problem this is likely to be. Using the library example you gave, and assuming 'basic' testing of the exported classes etc, you would discover the problem immediately. No harm done. In most other cases, you're either the producer of the parent, or a consumer of the immediate child and you'll discover the problem immediately. So, again no harm done. The only remaining case (I can see) is the one I mentioned in my other thread/reply. That of the end user swapping out the parent library, in which case your library is not recompiled and D can't help you here.. unless it throws a runtime exception for this case.. hmm. So.. adding abstract to the class definition doesn't seem to gain us much. You can argue, as Jonathan did that it's more consistent, or provides a visual clue to the programmer. But, the flip side is that it can be irritating to have to add it in the cases where it's already even with good intelisense and good auto code generation. Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011
find itWell, I believe I understand your argument and I admit that I didinstantiated.odd that the error was not detected until the class wasand IBut, once I thought about it I understood why this is the case,rather itbelieve you understand it as well. I also understand that you'dhow muchwasn't the case. I think the only area of disagreement here isof a problem this is likely to be.of theUsing the library example you gave, and assuming 'basic' testingNo harmexported classes etc, you would discover the problem immediately.parent, or adone. In most other cases, you're either the producer of theconsumer of the immediate child and you'll discover the problem immediately. So, again no harm done.otherThe only remaining case (I can see) is the one I mentioned in mylibrary, inthread/reply. That of the end user swapping out the parenthere..which case your library is not recompiled and D can't help youunless it throws a runtime exception for this case.. hmm.usSo.. adding abstract to the class definition doesn't seem to gainormuch. You can argue, as Jonathan did that it's more consistent,that itprovides a visual clue to the programmer. But, the flip side isalreadycan be irritating to have to add it in the cases where it'sobvious to anyone reading the code - I have moments like that(I really need to get a client for this - I just had to manually copy out your message and RegEx it to add the little >> marks) Well, specifically, here's what it *does* give you: It means that a class *not* marked as abstract is not intended by the programmer to be abstract (equivalent to explicitly marking something as nonabstract). If we accept that the default class declaration (class X {}) is non-abstract, then we probably don't need to consider explicit specification of non-abstraction. It means that a class *marked* as abstract is intended to be abstract. The difference is that the compiler can then decide at the definition of a class - where this error belongs - if a Class actually conforms to its contract of being abstract / concrete. No harm is done if sufficient testing in place, but it still puts the onus in *some* (probably quite specific or rare) circumstances on the user of my class, because it's feasible for my class to have been previously valid. On the other hand, I should not require a unittest just to ensure that my class is the same class it was between some (potentially unknown) change. That's not to say that I shouldn't test, but I shouldn't have to for this one, particular case. But D does not allow me to explicitly make it clear that my class is intended to be concrete, and it's relying on usage, rather than definition, for this information.even with good intelisense and good auto code generation.
Dec 02 2011
FWIW, I agree with Addam. One of the things I like in D is that the langage is designed so that interpretations are made based on what you intend, and what you intend to do is checked. That is exactly what is done by using overwrite keyword. If you intend to make a non-abstract class, the compiler should check it is non-abstract and complain if it is not. This way, the error message pops where it belongs, i.e. at the class definition, not somewhere in another file where it is instanciated. It is not hard to add a specific unittest close to the class definition to test instanciation, but this is still much harder than writing 'abstract' at the beginning of the class definition when you intend to make it abstract (which is also much shorter than writing a sentence to indicate that the class is abstract in the documentation). For special use-cases, when you don't know if the class is abstract or not because it is a template, you should just be allowed to write something like 'auto abstract' and the problem is solved. Is is a really a larger pain to make this correction early to the langage, than to keep this little misfeature for years ? With an appropriate (and optional) warning to tell abstract class will soon have to be declared such for several years before there is an (optional) error, and maybe even a patch to make the correction automatically, that should not be such a pain. -- Christophe
Dec 05 2011
On Fri, 02 Dec 2011 17:24:11 -0000, Adam <Adam anizi.com> wrote:Ok, fine, let me put it THIS way. Suppose I use a parent library, and *I* don't update it. The USER of my library provides an updated version for some unrelated reason.So.. the user has: Parent.dll Your.dll Their.exe and everything is working ok. Then they update Parent.dll to a new version. And because D does not require 'abstract' on classes, it all breaks? But.. Your.dll has not been recompiled.. so how is D supposed to detect this? Or, were you suggesting the user supply a new Parent.dll to you, and you rebuild Your.dll with it? In which case, when you run your unit tests you will get an error, right? Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011
Or I provide it as source, in which case, D *can* check it. But it means that when the parent is changed, my class type and instantiability implicitly changes, too.
Dec 02 2011
On Fri, 02 Dec 2011 17:44:44 -0000, Adam <Adam anizi.com> wrote:Or I provide it as source, in which case, D *can* check it. But it means that when the parent is changed, my class type and instantiability implicitly changes, too.True. This is a case I hadn't considered before. In this case the user will get an error instantiating your class and report a bug to you. Not ideal, but assuming you document the versions of the parent library you're compatible with, and run your unit tests against these it shouldn't happen and when it does it will be because the user upgraded the parent library past your compatibility guarantee, so it's on them. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011
On Fri, 02 Dec 2011 17:10:33 -0000, Adam <Adam anizi.com> wrote:So what do I have to do to prevent this? I have to *explicitly* check that Child is or is not instantiable, probably via unittest.YES! If you're releasing a library and not doing this... bad, bad, bad you :) -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011