www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Abstract functions in child classes

reply Adam <Adam Anizi.com> writes:
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
next sibling parent reply "Regan Heath" <regan netmail.co.nz> writes:
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
next sibling parent Adam <Adam anizi.com> writes:
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
prev sibling next sibling parent Adam <Adam Anizi.com> writes:
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
prev sibling parent Jacob Carlborg <doob me.com> writes:
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:

 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.

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. -Steve

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 Carlborg
Dec 01 2011
prev sibling next sibling parent reply =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
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
next sibling parent Adam <Adam anizi.com> writes:
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
prev sibling next sibling parent =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
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
prev sibling parent Jacob Carlborg <doob me.com> writes:
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:

 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?

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.

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 Carlborg
Dec 01 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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.

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. -Steve
Dec 01 2011
prev sibling next sibling parent reply mta`chrono <chrono mta-international.net> writes:
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
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
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
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
parent reply Adam <Adam Anizi.com> writes:
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
next sibling parent reply Adam <Adam Anizi.com> writes:
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
next sibling parent reply Adam <Adam anizi.com> writes:
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
parent Adam <Adam Anizi.com> writes:
 You're being a bit dramatic, no?  The code didn't compile, the

 caught it, and you have invented this theoretical case (which did

 occur) to try and make your point.  I don't deny that requiring

 has some value, but what I question is how much is that value?

 reasonable developer uses tests (or uses the code in question

 will end up instantiating any concrete class during testing (or

 I'd say the value is pretty close to zero (not zero, but very

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 avoid

 testing.  The error that occurs is not a sneaky silent one, it is

 compiler error.  The only risk is that the user of your library

 before you do, because you didn't test, but it still doesn't

 time an error occurs at compile time, it is a win, since the user

 accidentally use something incorrectly.  So here's the solution --

 tests.  In my opinion, breaking any existing code to add this

 is 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 not

 It may break in subtle ways that *do* compile.  If you are going

 on not testing your code after the base class changes, well, then

 you will have to get used to it failing.  Just be thankful if you

 compiler 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
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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 definition

A 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
parent reply Adam <Adam anizi.com> writes:
 A second possible use case:

 class C(T): T{
      // some declarations
 }

 Now you really want that template to be instantiable with T being

 an 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
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/02/2011 08:10 PM, Adam wrote:
 A second possible use case:

 class C(T): T{
       // some declarations
 }

 Now you really want that template to be instantiable with T being

 an 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.

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" // ... }
Dec 02 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/02/2011 09:05 PM, Timon Gehr wrote:
 On 12/02/2011 08:10 PM, Adam wrote:
 A second possible use case:

 class C(T): T{
 // some declarations
 }

 Now you really want that template to be instantiable with T being

 an 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.

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" // ... }

Oh, forgot to mention: This would not compile, if an explicit 'abstract' declaration on template class definitions was required.
Dec 02 2011
parent reply Adam <Adam Anizi.com> writes:
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
parent Timon Gehr <timon.gehr gmx.ch> writes:
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
prev sibling parent reply Adam <Adam Anizi.com> writes:
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
parent reply Adam <Adam Anizi.com> writes:
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
next sibling parent reply Adam <Adam Anizi.com> writes:
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
parent reply Adam <Adam anizi.com> writes:
 Well, I believe I understand your argument and I admit that I did


 odd that the error was not detected until the class was


 But, once I thought about it I understood why this is the case,


 believe you understand it as well.  I also understand that you'd


 wasn't the case.  I think the only area of disagreement here is


 of a problem this is likely to be.


 Using the library example you gave, and assuming 'basic' testing


 exported classes etc, you would discover the problem immediately.


 done.  In most other cases, you're either the producer of the


 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


 thread/reply.  That of the end user swapping out the parent


 which case your library is not recompiled and D can't help you


 unless it throws a runtime exception for this case.. hmm.


 So.. adding abstract to the class definition doesn't seem to gain


 much.  You can argue, as Jonathan did that it's more consistent,


 provides a visual clue to the programmer.  But, the flip side is


 can be irritating to have to add it in the cases where it's


 obvious to anyone reading the code - I have moments like that


 even with good intelisense and good auto code generation.


(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.
Dec 02 2011
parent travert phare.normalesup.org (Christophe) writes:
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
prev sibling parent Adam <Adam anizi.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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 definition

The 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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 02 Dec 2011 16:13:45 -0500, Adam <Adam anizi.com> wrote:

 You're being a bit dramatic, no?  The code didn't compile, the

 caught it, and you have invented this theoretical case (which did

 occur) to try and make your point.  I don't deny that requiring

 has some value, but what I question is how much is that value?

 reasonable developer uses tests (or uses the code in question

 will end up instantiating any concrete class during testing (or

 I'd say the value is pretty close to zero (not zero, but very

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...

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 way
 But let's assume for a moment that it's standard practice to avoid

 testing.  The error that occurs is not a sneaky silent one, it is

 compiler error.  The only risk is that the user of your library

 before you do, because you didn't test, but it still doesn't

 time an error occurs at compile time, it is a win, since the user

 accidentally use something incorrectly.  So here's the solution --

 tests.  In my opinion, breaking any existing code to add this

 is 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.

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.
 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
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
How can you put out a library where you don't even test your classes?
That's just bad programming, period.
Dec 02 2011
prev sibling next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
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
prev sibling next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
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
prev sibling next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
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
prev sibling next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
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 obvious to anyone reading the code - I have moments like that writing C#, even with good intelisense and good auto code generation. Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 02 2011
prev sibling next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
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
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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).

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.
 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