Thread overview
subclassing
Nov 02, 2010
spir
Nov 02, 2010
Jesse Phillips
Nov 02, 2010
spir
Nov 02, 2010
Jesse Phillips
Nov 02, 2010
Jonathan M Davis
Nov 02, 2010
Jesse Phillips
Nov 02, 2010
spir
Nov 02, 2010
Jesse Phillips
November 02, 2010
Hello,


After the exchanges on ranges, I am experimenting around this notion as a way to learn about D classes, subclassing, generics, etc...
In the code below, there are three obscure points for me:

* I wrote Range as class, but I rather meant an interface. D does not let me do that, apparently because there is no data slot in a D interface. Is then an interface a kind of data-less superclass? Or is there something I misunderstand?

* Why does D allow me redefining (data) slots in the subclass OddSquares, which exist in the superclass? (I did this first without noting, by copy & paste ;-)

* There is a bug in function find -- see DEBUG lines in this func and in main. For any reason, the passed range loses its data (.element==0). If I change find's interface to take a range of type OddSquares, then all runs fine. I don't know what more to do to find the bug...

(I hope you don't mind spaces before ';', I like to put purely syntactic thingies aside from proper code.)


Thank you for your help,
Denis

PS: I like very much this interface for input ranges :-) Decided to make output data plain state (slots T element & bool continues); only step() is a method. I know this is not true OO for some conception of OO, but it is ok for me. I also like in this design the fact that the constructor sets the range's initial state -- or is supposed to -- so that we can use the range at once, without calling step() once to initialise it.
Iteration using for as shown below is indeed equivalent to:
	auto range = ...;
	while (range.continues) {
		doSomethingWith(range.element);
		range.step();
	}
which is analog to traditional list traversal using
	while (node.next) {
		doSomethingWith(node.element);
		node = node.next;
	}
except the range interface can be applied to any kind of collection, taken globally or partially, even virtual like in the example of OddSquares below.


=================== code ======================
/+  experimentation on notion of range
+/

import std.stdio ;      // write*
import std.string ;		// join , format
import std.conv ;		// to!(destType)(expression)

class Range(T) {
    alias T type ;          // meta info
    // external state
    T element ;
    bool continues ;
    // methods
    abstract void step() ;
}

class OddSquares(T) : Range!T {
    alias T type ;          // meta info
    // external state
    T element ;
    bool continues ;
    // internal state
    private T number ;
    private T max ;
    // methods
    override void step() {
        if (this.number > this.max) {
            // continues range...
            this.continues = false ;
        } else {
            // ... or step once
            this.continues = true ;
            this.element = this.number * this.number ;
            this.number += 2 ;
        }
    }
    this(T min, T max) {
        // set initial internal state
        if (min%2 == 1)
            this.number = min ;
        else
            this.number = min+1 ;
        this.max = max ;
        // set initial external state
        this.step() ;
    }
}

T find (T) (OddSquares!T range , bool function(T element) predicate) {
    T element ;
    writeln("***", range.element) ;    // DEBUG
    for (; range.continues ; range.step) {
        element = range.element ;
        writeln(element) ;
        if (predicate(element))
            return element ;
    }
    return 0 ;  // placeholder, should be throw Exception
}

void main() {
    OddSquares!int range ;
    int element ;
    // traversal
    range = new OddSquares!int(1,9) ;
    writef ("elements of type %s: ", typeid(range.type)) ;
    for (; range.continues ; range.step) {
        element = range.element ;
        writef("%s ", element) ;
    }
    writeln() ;
    // find
    range = new OddSquares!int(10,20) ;
    writeln("***", range.element) ;    // DEBUG
    element = find!int(range, function bool(int e){return (e>200 && e<250);}) ;
    writeln("found element: ", element) ;
}

================== output =====================
=== buggy
elements of type int: 1 9 25 49 81
***121
***0
found element: 0

=== correct
elements of type int: 1 9 25 49 81
***121
***121
found element: 225
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

November 02, 2010
I'll come back with a more complete answer latter, but first.

spir Wrote:

> * I wrote Range as class, but I rather meant an interface. D does not let me do that, apparently because there is no data slot in a D interface. Is then an interface a kind of data-less superclass? Or is there something I misunderstand?

Interfaces describe what can something of that type can do, not what it has. Data fields are not allowed only function signatures and functions that perform local modifications.

> 	auto range = ...;
> 	while (range.continues) {
> 		doSomethingWith(range.element);
> 		range.step();
> 	}

Note that you are not actually using a Range as it is defined by D. In fact it is exactly the same, but named differently, here is what D's range interface looks like.


> 	auto range = ...;
> 	while (!range.empty) {
> 		doSomethingWith(range.front);
> 		range.popFront();
> 	}
November 02, 2010
On Tue, 02 Nov 2010 10:39:27 -0400
Jesse Phillips <jessekphillips+D@gmail.com> wrote:

> I'll come back with a more complete answer latter, but first.
> 
> spir Wrote:
> 
> > * I wrote Range as class, but I rather meant an interface. D does not let me do that, apparently because there is no data slot in a D interface. Is then an interface a kind of data-less superclass? Or is there something I misunderstand?
> 
> Interfaces describe what can something of that type can do, not what it has. Data fields are not allowed only function signatures and functions that perform local modifications.

Thank you for the clarification.

> > 	auto range = ...;
> > 	while (range.continues) {
> > 		doSomethingWith(range.element);
> > 		range.step();
> > 	}
> 
> Note that you are not actually using a Range as it is defined by D.

Yes, that's precisely what I meant with "experimenting around this notion" [of range].


Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

November 02, 2010
spir Wrote:


> * Why does D allow me redefining (data) slots in the subclass OddSquares, which exist in the superclass? (I did this first without noting, by copy & paste ;-)

This is either a bug or so that you don't have name clashes with all the super classes (could really reduce the available names).

> * There is a bug in function find -- see DEBUG lines in this func and in main. For any reason, the passed range loses its data (.element==0). If I change find's interface to take a range of type OddSquares, then all runs fine. I don't know what more to do to find the bug...

Nope, the bug is because you redefined element and so Range.element is not being assigned. Remove your duplicate declarations from OddSquares and it works.

I am also not sure why you have alias T type. T is already an alias for the type, and you never use it so maybe it is just having fun testing out alias.

> PS: I like very much this interface for input ranges :-) Decided to make output data plain state (slots T element & bool continues); only step() is a method. I know this is not true OO for some conception of OO, but it is ok for me.

This doesn't go against OOP in the least. The specification for a Range doesn't prevent the design you have choosen. The benefit to using functions is to calculate values instead of storing the state.

I understand you are trying to discover the benefits through your own design, but D Ranges would look something like this.

class OddSquares(T) {
    // internal state
    private T number ;
    private T max ;
    // methods

    @property bool empty() {
        if (this.number > this.max)
            return true;
        return false;
    }
    @property int front() {
        return this.number * this.number ;
    }
    void popFront() {
        this.number += 2 ;
    }
    this(T min, T max) {
        // set initial internal state
        if (min%2 == 1)
            this.number = min ;
        else
            this.number = min+1 ;
        this.max = max ;
    }
}


> I also like in this design the fact that the constructor sets the range's initial state -- or is supposed to -- so that we can use the range at once, without calling step() once to initialise it.

Ah, but you do call step at the very end of the constructor. This prevents it from entering user code and is generally all that matters. You will notice I acutally removed that call for the range above.

I guess you could say that the real complaint was due to the desire to time how long it took to load the data (build the ranges) and how long it took to do the filtering. That meant I was separating the initialization phase from the construction of the range itself. Any way you rarely see this creep into user code.

So the only other piece to inform you of (to help you learn D) is that find is found in std.algorithm and does exactly what you have demonstrated, with a slightly different call:

auto element = find!((int e){return (e>200 && e<250))(range);

or

auto element = find!("a>200 && a<250")(range);

And will return a range starting at the element found, however it will not stop when over 250. For that you use filter instead.

auto element = filter!("a>200 && a<250")(range);

November 02, 2010
On Tuesday, November 02, 2010 09:33:16 Jesse Phillips wrote:
> spir Wrote:
> > * Why does D allow me redefining (data) slots in the subclass OddSquares,
> > which exist in the superclass? (I did this first without noting, by copy
> > & paste ;-)
> 
> This is either a bug or so that you don't have name clashes with all the super classes (could really reduce the available names).
> 
> > * There is a bug in function find -- see DEBUG lines in this func and in main. For any reason, the passed range loses its data (.element==0). If I change find's interface to take a range of type OddSquares, then all runs fine. I don't know what more to do to find the bug...
> 
> Nope, the bug is because you redefined element and so Range.element is not being assigned. Remove your duplicate declarations from OddSquares and it works.
> 
> I am also not sure why you have alias T type. T is already an alias for the type, and you never use it so maybe it is just having fun testing out alias.
> 
> > PS: I like very much this interface for input ranges :-) Decided to make
> > output data plain state (slots T element & bool continues); only step()
> > is a method. I know this is not true OO for some conception of OO, but
> > it is ok for me.
> 
> This doesn't go against OOP in the least. The specification for a Range doesn't prevent the design you have choosen. The benefit to using functions is to calculate values instead of storing the state.
> 
> I understand you are trying to discover the benefits through your own design, but D Ranges would look something like this.
> 
> class OddSquares(T) {
>     // internal state
>     private T number ;
>     private T max ;
>     // methods
> 
>     @property bool empty() {
>         if (this.number > this.max)
>             return true;
>         return false;
>     }
>     @property int front() {
>         return this.number * this.number ;
>     }
>     void popFront() {
>         this.number += 2 ;
>     }
>     this(T min, T max) {
>         // set initial internal state
>         if (min%2 == 1)
>             this.number = min ;
>         else
>             this.number = min+1 ;
>         this.max = max ;
>     }
> }

I should point out that you forgot the save property, which is required for forward ranges (though not input ranges). Without it, any algorithm which processes the range will consume it. Also, (though the OP did use a class, so I assume that's why you did), ranges are usually done with structs which makes passing them around less of an issue since structs are value types while classes are reference types. There are several range definitions in Phobos - particularly in std.range, std.algorithm, and std.container if the OP wants to look at existing ones. And since ranges are heavily used in Phobos and they have to have a very specific set of functions to work with functions in Phobos, it would be a good idea for the OP to stick to the function names that Phobos uses (as you demonstrated) or code isn't going to work with Phobos' facilities, which makes ranges a lot less useful. Ranges are for a lot more than just iterating, after all.

- Jonathan M Davis
November 02, 2010
Jonathan M Davis Wrote:


> I should point out that you forgot the save property, which is required for forward ranges (though not input ranges). Without it, any algorithm which processes the range will consume it.

Trying to ease this guy into ranges.

I did notice though, the InputRange interface requires quite a few more methods than is required for an InputRange. Specifically, moveFront and opApply. So I guess my question ends up being, why (not to you specifically...)?

November 02, 2010
On Tue, 02 Nov 2010 12:33:16 -0400
Jesse Phillips <jessekphillips+D@gmail.com> wrote:

> spir Wrote:
> 
> 
> > * Why does D allow me redefining (data) slots in the subclass OddSquares, which exist in the superclass? (I did this first without noting, by copy & paste ;-)
> 
> This is either a bug or so that you don't have name clashes with all the super classes (could really reduce the available names).

I have name clashes, that's what I meant with "redefining". Test case:

class SC {int i;}
class C : SC {int i;}

compiles. If this (non-)behaviour of the compiler is not intended for a good reason, then it may be considered a bug. At least, we should get a warning, don't you think? Also, this is anyway bad design and goes against the fact that in D (and OO in general), class features are precisely identified by name... See also, bug below.

> > * There is a bug in function find -- see DEBUG lines in this func and in main. For any reason, the passed range loses its data (.element==0). If I change find's interface to take a range of type OddSquares, then all runs fine. I don't know what more to do to find the bug...
> 
> Nope, the bug is because you redefined element and so Range.element is not being assigned. Remove your duplicate declarations from OddSquares and it works.

Yes! That(s it, thank you (tested). I identified the dstrange behaviour of the compiler above, but did not relate it to the bug.

> I am also not sure why you have alias T type. T is already an alias for the type, and you never use it so maybe it is just having fun testing out alias.

It for possible use in other app. Gives me access for the actual type from an instance, under instance.type. Found the trick in the book "The D P.L.".

I take the opportunity to ask whether there is a way for an element to access its own type, say x.type. I found typeid, but its only a string. Also, is there any kind of Type type, with variables of it? (Similar to function variables.) Finally, can an element know its (variable) name? Or can we set it on itself, provided it's a struct or class instance with a dedicated field, for example by reading the scope? (This is an idiom in Python, but sure it's a dynamic language.)

> > PS: I like very much this interface for input ranges :-) Decided to make output data plain state (slots T element & bool continues); only step() is a method. I know this is not true OO for some conception of OO, but it is ok for me.
> 
> This doesn't go against OOP in the least. The specification for a Range doesn't prevent the design you have choosen. The benefit to using functions is to calculate values instead of storing the state.

Right.

> I understand you are trying to discover the benefits through your own design,

Exactly! I find this whole feature really attractive, but complex and abstract. (In such cases, try-&-do-it-yourself is a learning method that works well with me, esp to understand the how's and why's.)

> but D Ranges would look something like this.
> 
> class OddSquares(T) {
>     // internal state
>     private T number ;
>     private T max ;
>     // methods
> 
>     @property bool empty() {
>         if (this.number > this.max)
>             return true;
>         return false;
>     }
>     @property int front() {
>         return this.number * this.number ;
>     }
>     void popFront() {
>         this.number += 2 ;
>     }
>     this(T min, T max) {
>         // set initial internal state
>         if (min%2 == 1)
>             this.number = min ;
>         else
>             this.number = min+1 ;
>         this.max = max ;
>     }
> }

Great, thank you for the example. I don't understand the benefit of @property (which indeed does not seem to have the same meaning as in python.)

> > I also like in this design the fact that the constructor sets the range's initial state -- or is supposed to -- so that we can use the range at once, without calling step() once to initialise it.
> 
> Ah, but you do call step at the very end of the constructor. This prevents it from entering user code and is generally all that matters.

I just don't want to force client code to initialize, since anyway the range is useless before init. With init in constructor, they get the range ready to use.

T find (T) (Range!T range , bool function(T element) predicate) {
    T element ;
    range.step();				/+ *** avoid this *** +/
    for (; range.continues ; range.step()) {
        element = range.element ;
        if (predicate(element))
            return element ;
    }
    return 0 ;  // placeholder, should be throw Exception
}

> You will notice I acutally removed that call for the range above.
> 
> I guess you could say that the real complaint was due to the desire to time how long it took to load the data (build the ranges) and how long it took to do the filtering. That meant I was separating the initialization phase from the construction of the range itself. Any way you rarely see this creep into user code.

I do not really understand whether you mean it's better w/o init in constructor, and why.

> So the only other piece to inform you of (to help you learn D) is that find is found in std.algorithm and does exactly what you have demonstrated, with a slightly different call:
> 
> auto element = find!((int e){return (e>200 && e<250))(range);
> 
> or
> 
> auto element = find!("a>200 && a<250")(range);
> 
> And will return a range starting at the element found, however it will not stop when over 250. For that you use filter instead.
> 
> auto element = filter!("a>200 && a<250")(range);

I considered making find (and other funcs using ranges) return the range, as is described in the article. But as of now, the only advantage I could guess is chaining method calls.

[OT topic="style"]
I don't use such patterns often because I prefere to clearly expose steps, for ease of reading, esp with carefully chosen names. eg in Python:

def listText1(elements, sep='', lDelim='',rDelim=''):
    return "%s%s%s" %(lDelim , sep.join(str(e) for e in elements) , rDelim)

def listText2(elements, sep='', lDelim='',rDelim=''):
    elementTexts = (str(e) for e in elements)
    content = sep.join(elementTexts)
    return "%s%s%s" %(lDelim , content , rDelim)

I'm sure a non-pythonist can easily read listText2, even with the strange expression on 1st line, less sure about listText1 :-)
[/OT]



-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

November 02, 2010
On Tue, 02 Nov 2010 16:35:39 -0400, spir <denis.spir@gmail.com> wrote:

> On Tue, 02 Nov 2010 12:33:16 -0400
> Jesse Phillips <jessekphillips+D@gmail.com> wrote:
>
>> spir Wrote:
>>
>>
>> > * Why does D allow me redefining (data) slots in the subclass  
>> OddSquares, which exist in the superclass? (I did this first without noting, by copy & paste ;-)
>>
>> This is either a bug or so that you don't have name clashes with all the super classes (could really reduce the available names).
>
> I have name clashes, that's what I meant with "redefining". Test case:
>
> class SC {int i;}
> class C : SC {int i;}
>
> compiles. If this (non-)behaviour of the compiler is not intended for a good reason, then it may be considered a bug. At least, we should get a warning, don't you think? Also, this is anyway bad design and goes against the fact that in D (and OO in general), class features are precisely identified by name... See also, bug below.

The behavior is intentional.  It's similar to C++ behavior.  Usually, you only do it with private members which are not accessible to the derived class.

-Steve
November 02, 2010
spir Wrote:

> On Tue, 02 Nov 2010 12:33:16 -0400
> Jesse Phillips <jessekphillips+D@gmail.com> wrote:
> 
> > spir Wrote:
> > 
> > 
> > > * Why does D allow me redefining (data) slots in the subclass OddSquares, which exist in the superclass? (I did this first without noting, by copy & paste ;-)
> > 
> > This is either a bug or so that you don't have name clashes with all the super classes (could really reduce the available names).
> 
> I have name clashes, that's what I meant with "redefining". Test case:

No, a name clash would mean it complains that you already have a variable of the same name (i.e. the names clash).

I believe it is called shadowing, and it is actually common. Local/Class variables are able to shadow global variables. Local function variables can shadow class variables. As to whether this is best, I can't say. But I can say Java does it, and I don't think it is a common mistake:

http://ideone.com/0Ny9X

> > I am also not sure why you have alias T type. T is already an alias for the type, and you never use it so maybe it is just having fun testing out alias.
> 
> It for possible use in other app. Gives me access for the actual type from an instance, under instance.type. Found the trick in the book "The D P.L.".

Ah, yes. Though this is not needed with a Range. You can use std.range.ElementType to obtain the type returned from a range.

> I take the opportunity to ask whether there is a way for an element to access its own type, say x.type. I found typeid, but its only a string.

Yes, typeof(x);

> Also, is there any kind of Type type, with variables of it? (Similar to function variables.)

No, but you can look into TypeTuples and Tuples

http://digitalmars.com/d/2.0/phobos/std_typetuple.html http://digitalmars.com/d/2.0/phobos/std_typecons.html

> Finally, can an element know its (variable) name? Or can we set it on itself, provided it's a struct or class instance with a dedicated field, for example by reading the scope? (This is an idiom in Python, but sure it's a dynamic language.)

No and Yes. You can turn a (something) name into a string with .stringof

string hello;

hello = hello.stringof;

But at this point what you are probably looking for is to use an alias template parameter for the function you are calling.

auto someFunction(alias mayVar)() {
...
}

> > I understand you are trying to discover the benefits through your own design,
> 
> Exactly! I find this whole feature really attractive, but complex and abstract. (In such cases, try-&-do-it-yourself is a learning method that works well with me, esp to understand the how's and why's.)

Yeah, sometimes I think I can do something better so I go to build it. When I finish and look back at what I'm replacing, I realize... I did it the same way.

> Great, thank you for the example. I don't understand the benefit of @property (which indeed does not seem to have the same meaning as in python.)

Currently none. As mentioned in read-only, @propery isn't really implement yet. The benefit is to be able to call/asign to functions as though they were field variables.

> I just don't want to force client code to initialize, since anyway the range is useless before init. With init in constructor, they get the range ready to use.
> > You will notice I acutally removed that call for the range above.
> > 
> > I guess you could say that the real complaint was due to the desire to time how long it took to load the data (build the ranges) and how long it took to do the filtering. That meant I was separating the initialization phase from the construction of the range itself. Any way you rarely see this creep into user code.
> 
> I do not really understand whether you mean it's better w/o init in constructor, and why.

Purely how I wanted to use my range. Otherwise you shouldn't force the use of popFront() before using the range. I probably would have built my program differently had I known I wanted the separation I did.

> > So the only other piece to inform you of (to help you learn D) is that find is found in std.algorithm and does exactly what you have demonstrated, with a slightly different call:
> > 
> > auto element = find!((int e){return (e>200 && e<250))(range);
> > 
> > or
> > 
> > auto element = find!("a>200 && a<250")(range);
> > 
> > And will return a range starting at the element found, however it will not stop when over 250. For that you use filter instead.
> > 
> > auto element = filter!("a>200 && a<250")(range);
> 
> I considered making find (and other funcs using ranges) return the range, as is described in the article. But as of now, the only advantage I could guess is chaining method calls.
> 
> [OT topic="style"]
> I don't use such patterns often because I prefere to clearly expose steps, for ease of reading, esp with carefully chosen names. eg in Python:
> 
> def listText1(elements, sep='', lDelim='',rDelim=''):
>     return "%s%s%s" %(lDelim , sep.join(str(e) for e in elements) , rDelim)
> 
> def listText2(elements, sep='', lDelim='',rDelim=''):
>     elementTexts = (str(e) for e in elements)
>     content = sep.join(elementTexts)
>     return "%s%s%s" %(lDelim , content , rDelim)
> 
> I'm sure a non-pythonist can easily read listText2, even with the strange expression on 1st line, less sure about listText1 :-)
> [/OT]

But your example is chaining calls, just storing each peace before passing it on. In D elements and elementTexts are Ranges. And maybe you want to sort your list (creates a sorted Range), then only join the elements that start at 'j'. Each of these is a step that can build on the other whether stored in a variable or not.