View mode: basic / threaded / horizontal-split · Log in · Help
December 02, 2011
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
> 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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
> 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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
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
Re: Abstract functions in child classes
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
1 2 3 4 5
Top | Discussion index | About this forum | D home