www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Idea: Lazy upcasting

reply Marcin Kuszczak <aarti interia.pl> writes:
Hello!

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

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

abstract class Storage {
=C2=A0 =C2=A0 =C2=A0Storage param1(int p1) {
         this.p1 =3D p1;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return this;
=C2=A0 =C2=A0 =C2=A0}
private:
     int p1;
}

class SpecificStorage : Storage {
public:
=C2=A0 =C2=A0 =C2=A0SpecificStorage param2(bool p2) {
        this.p2=3Dp2;
        return this;
     }
private:
     bool p2;
}

void main() {
//                             ...ok...      ...ok...        ...ups!
SpecificStorage1 s1 =3D (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 implementatio=
n of=20
param1() in SpecificStorage:

=C2=A0 =C2=A0 =C2=A0SpecificStorage param1(int p1) {
         this.p1 =3D p1;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return this;
=C2=A0 =C2=A0 =C2=A0}


But my intuition about that would be that "this" pointer from method pa=
ram1=20
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 reimplemen=
t
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 descenda=
nt 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 re=
turn
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 =3D p1;
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return cast(Storage)thi=
s;
}

But usually there is a need to get opposite behaviour and get descendan=
t
class not ancestor...

-----

I would say that recently, when new dmd 1.010 appeard, such a change sh=
ould
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 =3D cast(SpecificStorage) Object.factory("SpecificSto=
rage");

With proposed change you will just get:
SpecificStorage s =3D 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 enoug=
h to
say that...=20

Anyway please comment on that...

--=20
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/d=
oost/
-------------------------------------
Mar 26 2007
next sibling parent "Denis Golovan" <denis remove.me.azovprom.com> writes:
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.
Mar 27 2007
prev sibling next sibling parent reply Davidl <Davidl 126.com> writes:
class baseclass
{
}
class inheritedclass:baseclass
{
     int j(){}
}
let's see what upcast would happen:

auto baseinst =3D new baseclass;
inheritedclass inheritedinst=3D 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 me=
an
 something like below:

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

 abstract class Storage {
 =C2=A0 =C2=A0 =C2=A0Storage param1(int p1) {
          this.p1 =3D p1;
 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return this;
 =C2=A0 =C2=A0 =C2=A0}
 private:
      int p1;
 }

 class SpecificStorage : Storage {
 public:
 =C2=A0 =C2=A0 =C2=A0SpecificStorage param2(bool p2) {
         this.p2=3Dp2;
         return this;
      }
 private:
      bool p2;
 }

 void main() {
 //                             ...ok...      ...ok...        ...ups!
 SpecificStorage1 s1 =3D (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 implementati=
on =
 of
 param1() in SpecificStorage:

 =C2=A0 =C2=A0 =C2=A0SpecificStorage param1(int p1) {
          this.p1 =3D p1;
 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return this;
 =C2=A0 =C2=A0 =C2=A0}


 But my intuition about that would be that "this" pointer from method  =
 param1
 will point to "SpecificStorage" when it is used in SpecificStorage. Bu=
t =
 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 reimpleme=
nt
 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 whe=
n
 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 =3D p1;
 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return cast(Storage)th=
is;
 }

 But usually there is a need to get opposite behaviour and get descenda=
nt
 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 s=
o
 usage will be like below:
 SpecificStorage s =3D cast(SpecificStorage)  =
 Object.factory("SpecificStorage");

 With proposed change you will just get:
 SpecificStorage s =3D 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 derive=
d
 classes just to get covariance work
 - coherent with covariance feature

 I don't know disadvantages, so I would like to hear your opinion. Mayb=
e =
 it
 is just technically difficult, but I don't know dmd compiler good enou=
gh =
 to
 say that...

 Anyway please comment on that...
Mar 27 2007
parent reply Aarti_pl <aarti interia.pl> writes:
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)
Mar 27 2007
parent reply Mitja Slenc <mslenc gmail.com> writes:
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
Mar 27 2007
next sibling parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
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
Mar 27 2007
prev sibling parent Marcin Kuszczak <aarti interia.pl> writes:
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/ -------------------------------------
Mar 27 2007
prev sibling parent reply Reiner Pope <some address.com> writes:
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
Mar 27 2007
next sibling parent reply Reiner Pope <some address.com> writes:
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
Mar 27 2007
parent BCS <ao pathlink.com> writes:
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 { ... } }
Mar 27 2007
prev sibling next sibling parent Marcin Kuszczak <aarti interia.pl> writes:
 
 How about:
 
.....
 
 That should already work.
 
 Cheers,
 
 Reiner
I put into my compiler: 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; } void main() { auto c = (new SpecificStorage).param1(4).param2(true); writefln(SpecificStorage.p1, " --- ", SpecificStorage.p2); } ... and get errors using DMD 1.009 on Linux: covariance.d(5): class covariance.StorageImpl!(SpecificStorage).StorageImpl unable to resolve forward reference in definition covariance.d(16): class covariance.SpecificStorage unable to resolve forward reference in definition Errors on declaration of "class StorageImpl(T)" and "class SpecificStorage". To say true I don't know why they appear - IMHO it should compile. Maybe it's bug? -- 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/ -------------------------------------
Mar 27 2007
prev sibling parent Marcin Kuszczak <aarti interia.pl> writes:
Reiner Pope wrote:
 
 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
I get problems with compiling this (I put info in another post). But even if it would work I think that problem should be just fixed not only worked around. For my own "hot-fix" workaround I used template mixin. The problem is that solution is just complicated for simple use. It's *unnecessary* complicated as it can be solved by fixed current clumsy covariant feature. Best what could be worked out up till now is in: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=51241 (It's my before previous post.) -- 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/ -------------------------------------
Mar 27 2007