Jump to page: 1 2
Thread overview
Idea: Lazy upcasting
Mar 26, 2007
Marcin Kuszczak
Re: Lazy upcasting
Mar 27, 2007
Denis Golovan
Mar 27, 2007
Davidl
Mar 27, 2007
Aarti_pl
Mar 27, 2007
Mitja Slenc
Mar 27, 2007
Marcin Kuszczak
Mar 27, 2007
Reiner Pope
Mar 27, 2007
Reiner Pope
Mar 27, 2007
BCS
Mar 27, 2007
Marcin Kuszczak
Mar 27, 2007
Marcin Kuszczak
March 26, 2007
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
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
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
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
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
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
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
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
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
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