View mode: basic / threaded / horizontal-split · Log in · Help
March 26, 2007
Idea: Lazy upcasting
Hello!

Recently I have tried to achieve chaining a few calls to setters. I mean
something like below:

//-------------------------------------------

abstract class Storage {
     Storage param1(int p1) {
        this.p1 = p1;
         return this;
     }
private:
    int p1;
}

class SpecificStorage : Storage {
public:
     SpecificStorage param2(bool p2) {
       this.p2=p2;
       return this;
    }
private:
    bool p2;
}

void main() {
//                             ...ok...      ...ok...        ...ups!
SpecificStorage1 s1 = (new SpecificStorage).param2(true).param1(5);
}

//-------------------------------------------

Because returned type from param1 is Storage above example doesn't work...

Unfortunately currently it is necessary to add overridden implementation of 
param1() in SpecificStorage:

     SpecificStorage param1(int p1) {
        this.p1 = p1;
         return this;
     }


But my intuition about that would be that "this" pointer from method param1 
will point to "SpecificStorage" when it is used in SpecificStorage. But it
is upcasted to Storage during return from param1.

Such a behavior seems a nonsense when there is covariance return type
feature in programming language. To get covariance I have to reimplement
same code in every inherited class just to trigger proper behaviour.

Here I would like to propose a change:

****
There should be no upcast during return. When function returns descendant of
declared type it should not be converted to base class (declared) but
function should return descendant type. I think about this as a lazy
upcasting :-)
****

It should not break anything when instead of making upcasting during return
compiler just return concreate type and probably later make upcast when
e.g. assigning to Storage variable. When I want to assure that Storage
class will be returned I just can add cast in base class like below:

Storage param1(int p1) {
       this.p1 = p1;
        return cast(Storage)this;
}

But usually there is a need to get opposite behaviour and get descendant
class not ancestor...

-----

I would say that recently, when new dmd 1.010 appeard, such a change should
additionally improve language. There is a new method in Object:

static Object factory(char[]);

It will be usually used to create specific classes, not Object class so
usage will be like below:
SpecificStorage s = cast(SpecificStorage) Object.factory("SpecificStorage");

With proposed change you will just get:
SpecificStorage s = Object.factory("SpecificStorage");

Advantages:
- downcasts are no more necessary (there are no casts at all, although it
could look like there are implicit downcasts)
- no need for reimplementation of same method returning this in derived
classes just to get covariance work
- coherent with covariance feature

I don't know disadvantages, so I would like to hear your opinion. Maybe it
is just technically difficult, but I don't know dmd compiler good enough to
say that... 

Anyway please comment on that...

-- 
Regards
Marcin Kuszczak (Aarti_pl)
-------------------------------------
Ask me why I believe in Jesus - http://zapytaj.dlajezusa.pl (en/pl)
Doost (port of few Boost libraries) - http://www.dsource.org/projects/doost/
-------------------------------------
March 27, 2007
Re: Lazy upcasting
Hi, all!

>> void main() {
>> //                             ...ok...      ...ok...        ...ups!
>> SpecificStorage1 s1 = (new SpecificStorage).param2(true).param1(5);
>>}

As far as I understand, this breaks strict type-checking of the language.

However, things like overriding virtual function with params/return types 
changed to subclasses, IMHO, is worth to consider. Indeed, it shortens 
overriding of semantically same methods.

As for object factory function, it seems that is used for hiding subclasses, 
so, IMHO, it don't need downcasts at all.
March 27, 2007
Re: Idea: Lazy upcasting
class baseclass
{
}
class inheritedclass:baseclass
{
    int j(){}
}
let's see what upcast would happen:

auto baseinst = new baseclass;
inheritedclass inheritedinst= cast(inheritedclass)cast(void*) baseinst;
inheritedinst.j();        <--- oops , AV here

implicitly upcasting could cause tons of bugs

and how do u know when u don't want implicit casting but it casts?



> Hello!
>
> Recently I have tried to achieve chaining a few calls to setters. I mean
> something like below:
>
> //-------------------------------------------
>
> abstract class Storage {
>      Storage param1(int p1) {
>          this.p1 = p1;
>          return this;
>      }
> private:
>      int p1;
> }
>
> class SpecificStorage : Storage {
> public:
>      SpecificStorage param2(bool p2) {
>         this.p2=p2;
>         return this;
>      }
> private:
>      bool p2;
> }
>
> void main() {
> //                             ...ok...      ...ok...        ...ups!
> SpecificStorage1 s1 = (new SpecificStorage).param2(true).param1(5);
> }
>
> //-------------------------------------------
>
> Because returned type from param1 is Storage above example doesn't  
> work...
>
> Unfortunately currently it is necessary to add overridden implementation  
> of
> param1() in SpecificStorage:
>
>      SpecificStorage param1(int p1) {
>          this.p1 = p1;
>          return this;
>      }
>
>
> But my intuition about that would be that "this" pointer from method  
> param1
> will point to "SpecificStorage" when it is used in SpecificStorage. But  
> it
> is upcasted to Storage during return from param1.
>
> Such a behavior seems a nonsense when there is covariance return type
> feature in programming language. To get covariance I have to reimplement
> same code in every inherited class just to trigger proper behaviour.
>
> Here I would like to propose a change:
>
> ****
> There should be no upcast during return. When function returns  
> descendant of
> declared type it should not be converted to base class (declared) but
> function should return descendant type. I think about this as a lazy
> upcasting :-)
> ****
>
> It should not break anything when instead of making upcasting during  
> return
> compiler just return concreate type and probably later make upcast when
> e.g. assigning to Storage variable. When I want to assure that Storage
> class will be returned I just can add cast in base class like below:
>
> Storage param1(int p1) {
>         this.p1 = p1;
>         return cast(Storage)this;
> }
>
> But usually there is a need to get opposite behaviour and get descendant
> class not ancestor...
>
> -----
>
> I would say that recently, when new dmd 1.010 appeard, such a change  
> should
> additionally improve language. There is a new method in Object:
>
> static Object factory(char[]);
>
> It will be usually used to create specific classes, not Object class so
> usage will be like below:
> SpecificStorage s = cast(SpecificStorage)  
> Object.factory("SpecificStorage");
>
> With proposed change you will just get:
> SpecificStorage s = Object.factory("SpecificStorage");
>
> Advantages:
> - downcasts are no more necessary (there are no casts at all, although it
> could look like there are implicit downcasts)
> - no need for reimplementation of same method returning this in derived
> classes just to get covariance work
> - coherent with covariance feature
>
> I don't know disadvantages, so I would like to hear your opinion. Maybe  
> it
> is just technically difficult, but I don't know dmd compiler good enough  
> to
> say that...
>
> Anyway please comment on that...
>
March 27, 2007
Re: Idea: Lazy upcasting
Davidl napisał(a):
> class baseclass
> {
> }
> class inheritedclass:baseclass
> {
>     int j(){}
> }
> let's see what upcast would happen:
> 
> auto baseinst = new baseclass;
> inheritedclass inheritedinst= cast(inheritedclass)cast(void*) baseinst;
> inheritedinst.j();        <--- oops , AV here

This is example of downcast - casting from base class to inherited 
class. This operation can always be dangerous.

But clue of my idea is to AVOID downcasting. There should be no casting 
 at all in case which I have described in my proposition.


> 
> implicitly upcasting could cause tons of bugs
> 
> and how do u know when u don't want implicit casting but it casts?
> 
> 

Proposition has also nothing to do with implicit downcasting. It has 
more with upcasting, but in opposite way - implicit upcasting should be 
disallowed in some cases :-)

If my proposition is not clear in some aspects, feel free to ask 
questions. I am not a native speaker, so parts of this proposition could 
be unclear...

Regards
Marcin Kuszczak
(aarti_pl)
March 27, 2007
Re: Idea: Lazy upcasting
Aarti_pl wrote:

> Proposition has also nothing to do with implicit downcasting. It has 
> more with upcasting, but in opposite way - implicit upcasting should be 
> disallowed in some cases :-)
> 
> If my proposition is not clear in some aspects, feel free to ask 
> questions. I am not a native speaker, so parts of this proposition could 
> be unclear...

OK, let me try to restate the problem, to see if I get it - some methods 
always return this, so that several invocations on the same object can 
be chained together : foo.a().b().c(). But, if one defines an inherited 
class and doesn't override those methods to indicate the new return 
type, calling base class' methods results in losing information about 
the derived type, so neither can the object be assigned to references of 
the derived type, nor can new methods of the derived class be called 
after calling base methods.

The problem with your suggestion of 'lazy upcasting' is that it's quite 
impossible for the compiler to verify whether all the methods in fact 
return this (for example, the source file could've been compiled 
previously and its source is no longer available), so it can't tell 
whether to do the usual thing (use the declared return type) or not (use 
the derived type).

A better idea would be to use a special keyword for the return type to 
indicate what is going on:

class Base
{
	return_this foo(int i) { ... }
}

class Derived : Base
{
	return_this bar(int i) { ... }
}

void main()
{
	Derived d = (new Derived).foo(1).bar(2); // currently an error
}


Another idea would be to treat void methods as if they implicitly 
returned this, though I'm quite sure there are hidden dangers in doing 
so. But, method definitions could certainly be cleaner and more obvious 
(it's not always clear whether the object returned is supposed to be a 
new one, or the called one), and I guess relatively many temporary 
variables serve only the purpose of being able to call more than one 
method on a given object (without fetching it twice from somewhere), and 
those would often no longer be needed, as well.


xs0
March 27, 2007
Re: Idea: Lazy upcasting
Mitja Slenc wrote:
> Aarti_pl wrote:
> 
>> Proposition has also nothing to do with implicit downcasting. It has 
>> more with upcasting, but in opposite way - implicit upcasting should 
>> be disallowed in some cases :-)
>>
>> If my proposition is not clear in some aspects, feel free to ask 
>> questions. I am not a native speaker, so parts of this proposition 
>> could be unclear...
> 
[...snip...]
> A better idea would be to use a special keyword for the return type to 
> indicate what is going on:
> 
> class Base
> {
>     return_this foo(int i) { ... }
> }
> 
> class Derived : Base
> {
>     return_this bar(int i) { ... }
> }
> 
> void main()
> {
>     Derived d = (new Derived).foo(1).bar(2); // currently an error
> }

I was thinking roughly the same thing, although maybe we could use an operator like symbol 
rather than a keyword.  Something like '@' maybe, where 'Object@' would read as "Object, 
or anything derived there-from."

class Base {
  Base@ foo (int i) { ... }
}

And make the general rule that such return types are implicitly cast to the lowest 
possible class.  When used for type-inferance, however, they should probably still count 
as the highest class... that could be an issue.  For example:

Given...
Base@ someFunc () { ... }

This should work so long as someFunc returns a Derived.
Derived obj = someFunc();

And this should also work, but type 'obj' as a Base.
auto obj = someFunc();


If D had an 'instanceof' operator or close equivelant, it would've made a nice re-usable 
for this case.  Alas.

-- Chris Nicholson-Sauls
March 27, 2007
Re: Idea: Lazy upcasting
Mitja Slenc wrote:

> 
> OK, let me try to restate the problem, to see if I get it - some methods
> always return this, so that several invocations on the same object can
> be chained together : foo.a().b().c(). But, if one defines an inherited
> class and doesn't override those methods to indicate the new return
> type, calling base class' methods results in losing information about
> the derived type, so neither can the object be assigned to references of
> the derived type, nor can new methods of the derived class be called
> after calling base methods.

Yes. That is exactly the problem I was writing about....

> 
> The problem with your suggestion of 'lazy upcasting' is that it's quite
> impossible for the compiler to verify whether all the methods in fact
> return this (for example, the source file could've been compiled
> previously and its source is no longer available), so it can't tell
> whether to do the usual thing (use the declared return type) or not (use
> the derived type).

You are probably right. I was also thinking about problems with automatic
type inference with 'auto' - in this case compiler really can not know what
returned type is. For object type it can be any class which is possible to
create it. Knowing just method signature tells nothing about returned type.
Without knowledge of function source code compiler can do nothing...


> 
> A better idea would be to use a special keyword for the return type to
> indicate what is going on:
> 
> class Base
> {
> return_this foo(int i) { ... }
> }
> 
> class Derived : Base
> {
> return_this bar(int i) { ... }
> }
> 
> void main()
> {
> Derived d = (new Derived).foo(1).bar(2); // currently an error
> }
> 

Wow... IMHO it is stike home! This looks like it solves problem cleanly.
(Main problem, as it not helps Object.factory() case, but it is probably
not so easy solvable if at all).

I would just propose other syntax:

class Base {
       this foo(int i) { return this; } // function declared as returning "this" 
                                        // in base class

       this other() { return this; }    // same as above
}

class Derived : Base {
       this foo(int i) { return this; } // function overriding base class 
                                        // returns Derived
       this bar(int i) { return this; } // another function which will return this
       Base foo1() {} // foo1 returns always Base class instance
       Derived bar() {} // always returns Derived
       // in derived class method 'other' from base class is also available. 
       // it returns Derived.
}

void main() {
       // hopefully it will compile with 
       // DMD 1.xxx > DMD 1.010   :-)
       Derived d = (new Derived).foo(1).bar(2).other; 
}

It looks like really good replacement for clumsy covariance return type
feature. (In case of covariance return type it's difficult to see why
function in derived class with totally different return type should
override method in base class; and of course there is still problem with
what I have pointed out at the beginning...).

Return type 'this' should be just another special return type, like 'void'
type is currently. This special type shows precisely what will be returned
from function. It is possible to say exactly what return type will be just
seeing method (in class) declaration. It's consistent and looks good. It's
also probably even easier to work with it for compiler.

I wish it would be implemented. Walter, can you comment on this?

> 
> Another idea would be to treat void methods as if they implicitly
> returned this, though I'm quite sure there are hidden dangers in doing
> so. But, method definitions could certainly be cleaner and more obvious
> (it's not always clear whether the object returned is supposed to be a
> new one, or the called one), and I guess relatively many temporary
> variables serve only the purpose of being able to call more than one
> method on a given object (without fetching it twice from somewhere), and
> those would often no longer be needed, as well.
> 
> 
> xs0


-- 
Regards
Marcin Kuszczak (Aarti_pl)
-------------------------------------
Ask me why I believe in Jesus - http://zapytaj.dlajezusa.pl (en/pl)
Doost (port of few Boost libraries) - http://www.dsource.org/projects/doost/
-------------------------------------
March 27, 2007
Re: Idea: Lazy upcasting
Marcin Kuszczak wrote:
> Hello!
> 
> Recently I have tried to achieve chaining a few calls to setters. I mean
> something like below:
> 
> //-------------------------------------------
> 
> abstract class Storage {
>      Storage param1(int p1) {
>          this.p1 = p1;
>          return this;
>      }
> private:
>      int p1;
> }
> 
> class SpecificStorage : Storage {
> public:
>      SpecificStorage param2(bool p2) {
>         this.p2=p2;
>         return this;
>      }
> private:
>      bool p2;
> }
> 
> void main() {
> //                             ...ok...      ...ok...        ...ups!
> SpecificStorage1 s1 = (new SpecificStorage).param2(true).param1(5);
> }
> 
> //-------------------------------------------

How about:

interface Storage
{
    Storage param1(int);
}

abstract class StorageImpl(T) : Storage {
    static assert(is(typeof(this) == T));

    T param1(int p1)
    {
        this.p1 = p1;
        return cast(T)this;
    }
    private int p1;
}

class SpecificStorage : StorageImpl!(SpecificStorage) {
    SpecificStorage param2(bool p2) {
        this.p2 = p2;
        return this;
    }
    private bool p2;
}

That should already work.

Cheers,

Reiner
March 27, 2007
Re: Idea: Lazy upcasting
Reiner Pope wrote:
> Marcin Kuszczak wrote:
>> Hello!
>>
>> Recently I have tried to achieve chaining a few calls to setters. I mean
>> something like below:
>>
>> //-------------------------------------------
>>
>> abstract class Storage {
>>      Storage param1(int p1) {
>>          this.p1 = p1;
>>          return this;
>>      }
>> private:
>>      int p1;
>> }
>>
>> class SpecificStorage : Storage {
>> public:
>>      SpecificStorage param2(bool p2) {
>>         this.p2=p2;
>>         return this;
>>      }
>> private:
>>      bool p2;
>> }
>>
>> void main() {
>> //                             ...ok...      ...ok...        ...ups!
>> SpecificStorage1 s1 = (new SpecificStorage).param2(true).param1(5);
>> }
>>
>> //-------------------------------------------
> 
> How about:
> 
> interface Storage
> {
>     Storage param1(int);
> }
> 
> abstract class StorageImpl(T) : Storage {
>     static assert(is(typeof(this) == T));
> 
>     T param1(int p1)
>     {
>         this.p1 = p1;
>         return cast(T)this;
>     }
>     private int p1;
> }
> 
> class SpecificStorage : StorageImpl!(SpecificStorage) {
>     SpecificStorage param2(bool p2) {
>         this.p2 = p2;
>         return this;
>     }
>     private bool p2;
> }
> 
> That should already work.
> 
> Cheers,
> 
> Reiner

PS. The reason I needed to add the extra interface was that D's type 
system doesn't think StorageImpl!(Derived) is-a StorageImpl!(Base)

It makes sense, because there are times when such a conversion is dangerous:
   List!(Square) a;
   List!(Shape) b = a;
   b.add(new Triangle); /// Ooops

But Java and Nemerle support this kind of casting in a safe way (I 
think). What you need to do is specify something in the template 
parameters at the top, which says, "You can cast down from this" or "you 
can cast up from this".  The general rule is that the "you can cast down 
from this" types may only be used as return values, and the "you can 
cast up from this" types may only be used as parameters:

interface Reader(T) // you can cast down from this
{
    T read();
}
// Reader!(Derived) is-a Reader!(Base)

interface Writer(T) // you can cast up from this
{
    void write(T);
}
// Writer!(Base) is-a Writer!(Derived)

D runs into problems because of the fact that templates can do more than 
simple type substitution; things like static if. I don't know what the 
solution to this is, but it seems like something here has been kept in 
mind for a while, given that the Future page on digitalmars says 
'template inheritance.'

Cheers,

Reiner
March 27, 2007
Re: Idea: Lazy upcasting
Reply to Reiner,

> PS. The reason I needed to add the extra interface was that D's type
> system doesn't think StorageImpl!(Derived) is-a StorageImpl!(Base)
> 
> It makes sense, because there are times when such a conversion is
> dangerous:
> List!(Square) a;
> List!(Shape) b = a;
> b.add(new Triangle); /// Ooops
> But Java and Nemerle support this kind of casting in a safe way (I
> think). What you need to do is specify something in the template
> parameters at the top, which says, "You can cast down from this" or
> "you can cast up from this".  The general rule is that the "you can
> cast down from this" types may only be used as return values, and the
> "you can cast up from this" types may only be used as parameters:
> 
> interface Reader(T) // you can cast down from this
> {
> T read();
> }
> // Reader!(Derived) is-a Reader!(Base)
> 
> interface Writer(T) // you can cast up from this
> {
> void write(T);
> }
> // Writer!(Base) is-a Writer!(Derived)
> 

I haven't been following what you are looking for but this might get some 
of what you want.

template Foo(T)
{
 static if(is(T.superof))
   interface Foo : Foo!(T.superof)
   {
      ...
   }
 else
  interface Foo
  {
     ...
  }
}
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home