December 02, 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 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.
December 02, 2011
> 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.

Could you expand on this case a bit? I'm not sure I follow the point one way or another.
December 02, 2011
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
> either
>> 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"

	// ...
}
December 02, 2011
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
>> either
>>> 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.
December 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?
December 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
December 02, 2011
> 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).


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

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

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.
December 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
December 02, 2011
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
> 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).
>
>
> 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
> 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.
>
> 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
December 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