www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - problems with Rebindable

reply chmike <christophe meessen.net> writes:
This thread is a followup of 
https://forum.dlang.org/post/vuljzyufphsywzevujhz forum.dlang.org 
with a refocused subject and question.

I'm looking for a mutable reference to a none mutable object to 
implement the flyweight pattern. It is for a library and its user 
interface. So I'm not looking for hacks to get around the problem.

Here is a toy example showing what I try to achieve and that 
fails to compile.
The main shows what I expect users should be able to do.

The aim is to allow the user to define another sets of constants 
of base type Info in his own class (e.g. MyInfos) and with a 
different implementation that the one provided in the example 
class Infos.

----
import std.stdio;
import std.typecons;


interface Info { }

class Infos {

     static class Obj : Info
     {
         this(string msg) immutable { this.msg = msg; }
         private string msg;
         string toString() immutable { return msg; }
     }

     static immutable one = new immutable Obj("I'm one");
}



void main()
{
     Rebindable!Info x1, x2 = Infos.one;

     assert(x1 is null);
     assert(x1 == null);

     assert(x2 !is null);
     assert(x2 != null);

     assert(x2 is Infos.one);
     assert(x2 == Infos.one);

     x1 = x2;
     assert(x1 is x2);
     assert(x1 == x2);

     assert(x1 is Infos.one);
     assert(x1 == Infos.one);

     writeln(x1);

     switch(x1)
     {
     case Infos.one: writeln("case Infos.one"); break;
     default: writeln("default"); break;
     }

     // That is enough for today
}
----

Compiling this code with dmd
$ dmd --version
DMD64 D Compiler v2.071.0
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright

I get the following errors:
$ dub build
Performing "debug" build using dmd for x86_64.
testrebindable ~master: building configuration "application"...
source/app.d(23,27): Error: cannot implicitly convert expression 
(one) of type immutable(Obj) to app.Info
source/app.d(26,12): Error: use 'is' instead of '==' when 
comparing with null
source/app.d(29,12): Error: use '!is' instead of '!=' when 
comparing with null
source/app.d(43,5): Error: 'x1' must be of integral or string 
type, it is a app.Info
source/app.d(45,10): Error: cannot implicitly convert expression 
(one) of type immutable(Obj) to app.Info
dmd failed with exit code 1.
May 21 2016
next sibling parent reply chmike <christophe meessen.net> writes:
On Saturday, 21 May 2016 at 10:42:13 UTC, chmike wrote:
 source/app.d(23,27): Error: cannot implicitly convert 
 expression (one) of type immutable(Obj) to app.Info
Apparently Rebindable doesn't support polymorphism. This is hopefully fixable.
 source/app.d(43,5): Error: 'x1' must be of integral or string 
 type, it is a app.Info
 source/app.d(45,10): Error: cannot implicitly convert 
 expression (one) of type immutable(Obj) to app.Info
A Rebindable variable can't be used as a switch argument. This would require a change to the language rules. However, the static immutable object Infos.one can be used as a case argument. The conclusion is that Rebindable doesn't cover the needs of a mutable object reference. In the flyweight pattern we only need to compare object addresses and we also want to use the lazy pattern to instantiate the immutable instances.
May 21 2016
parent ag0aep6g <anonymous example.com> writes:
On 05/21/2016 02:17 PM, chmike wrote:
 On Saturday, 21 May 2016 at 10:42:13 UTC, chmike wrote:
 source/app.d(23,27): Error: cannot implicitly convert expression (one)
 of type immutable(Obj) to app.Info
Apparently Rebindable doesn't support polymorphism. This is hopefully fixable.
No, the difference in mutability is the problem. Can't implicitly convert class objects from immutable to mutable. [...]
 A Rebindable variable can't be used as a switch argument. This would
 require a change to the language rules.
Class objects in general can't be used as switch arguments.
 However, the static immutable
 object Infos.one can be used as a case argument.
How do you figure that? I get 'Error: case must be a string or an integral constant, not Obj("I'm one")'.
 The conclusion is that Rebindable doesn't cover the needs of a mutable
 object reference.
I don't follow. Seems to me that the limitations you see here are in place for ordinary class objects as well.
 In the flyweight pattern we only need to compare object addresses and we
 also want to use the lazy pattern to instantiate the immutable instances.
It may be possible to get pointers from the objects and switch over those pointers. Can't use class objects directly, because equality is defined by opEquals which is virtual. And a switch doesn't make sense if opEquals needs to be called. Also, I'm afraid you're going to have a bad time when trying to get lazy initialization + immutable.
May 21 2016
prev sibling next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 05/21/2016 12:42 PM, chmike wrote:
      Rebindable!Info x1, x2 = Infos.one;
Rebindable!(immutable Info) x1, x2 = Infos.one;
May 21 2016
parent reply chmike <christophe meessen.net> writes:
On Saturday, 21 May 2016 at 13:17:11 UTC, ag0aep6g wrote:
 On 05/21/2016 12:42 PM, chmike wrote:
      Rebindable!Info x1, x2 = Infos.one;
Rebindable!(immutable Info) x1, x2 = Infos.one;
Indeed. Thanks. Reading the unit tests in the source code and the implementation of Rebindable helped. Note however that it doesn't work with immutable. It only works with constant. I guess this is because immutable is "stronger" than const. I determined that only const was supported by looking at Rebindable's code. Here is the code that finally works as I want. The flyweight pattern is thus well supported with the exception that switch can't be used. using static functions to get the Infos.one also allow to implement lazy object instantiation. ---- import std.stdio; import std.typecons; interface IInfo { string toString() const; } // alias Rebindable!(immutable IInfo) Info; <-- Doesn't compile alias Rebindable!(const IInfo) Info; class Infos { static class Obj : IInfo { this(string msg) { this.msg = msg; } private string msg; override string toString() const { return msg; } } static Info one() { static auto x = Info(new Obj("I'm one")); return x; } static Info two() { static auto x = Info(new Obj("I'm two")); return x; } } void main() { Info x1; Info x2 = Infos.one; assert(x1 is null); assert(x2 !is null); assert(x2 is Infos.one); assert(x2 == Infos.one); x1 = x2; assert(x1 is x2); assert(x1 == x2); assert(x1 is Infos.one); assert(x1 == Infos.one); writeln(x1); Info x3 = Info(new Infos.Obj("I'm one")); assert(x1 !is x3); assert(x1 != x3); // Because there is no opEqual for deep equality test IInfo o1 = new Infos.Obj("I'm one"), o2 = new Infos.Obj("I'm one"); assert(o1 !is o2); assert(o1 != o2); // What I need for the flyweight pattern /* -- Doesn't compile : x1 is not a string or integral value switch(x1) { case Infos.one: writeln("case Infos.one"); break; default: writeln("default"); break; } */ } I wasn't indeed using Rebindable correctly and it support only const objects, not immutable objects. Thank you everybody for your help.
May 21 2016
next sibling parent ag0aep6g <anonymous example.com> writes:
On 05/21/2016 03:36 PM, chmike wrote:
 Note however that it doesn't work with immutable. It only works with
 constant. I guess this is because immutable is "stronger" than const. I
 determined that only const was supported by looking at Rebindable's code.

 Here is the code that finally works as I want. The flyweight pattern is
 thus well supported with the exception that switch can't be used.

 using static functions to get the Infos.one also allow to implement lazy
 object instantiation.
[...]
 I wasn't indeed using Rebindable correctly and it support only const
 objects, not immutable objects.
I think your conclusion is wrong. Works fine if you add a couple `immutable`s (and change one `IInfo` to `Info`): ---- import std.stdio; import std.typecons; interface IInfo { string toString() const; } alias Rebindable!(immutable IInfo) Info; // Compiles just fine. class Infos { static class Obj : IInfo { this(string msg) immutable { this.msg = msg; } private string msg; override string toString() const { return msg; } } static Info one() { static auto x = Info(new immutable Obj("I'm one")); return x; } static Info two() { static auto x = Info(new immutable Obj("I'm two")); return x; } } void main() { Info x1; Info x2 = Infos.one; assert(x1 is null); assert(x2 !is null); assert(x2 is Infos.one); assert(x2 == Infos.one); x1 = x2; assert(x1 is x2); assert(x1 == x2); assert(x1 is Infos.one); assert(x1 == Infos.one); writeln(x1); Info x3 = Info(new immutable Infos.Obj("I'm one")); assert(x1 !is x3); assert(x1 != x3); // Because there is no opEqual for deep equality test Info o1 = new immutable Infos.Obj("I'm one"), o2 = new immutable Infos.Obj("I'm one"); assert(o1 !is o2); assert(o1 != o2); // What I need for the flyweight pattern /* -- Doesn't compile : x1 is not a string or integral value switch(x1) { case Infos.one: writeln("case Infos.one"); break; default: writeln("default"); break; } */ } ---- I thought marking the constructor `pure` would make it possible to implicitly convert a mutable `new` expression to immutable, but I couldn't get that to work. That would avoid all those `immutable`s. I'm probably forgetting something here.
May 21 2016
prev sibling parent Kagamin <spam here.lot> writes:
On Saturday, 21 May 2016 at 13:36:02 UTC, chmike wrote:
     static Info one()
     {
         static auto x = Info(new Obj("I'm one"));
         return x;
     }
     static Info two()
     {
         static auto x = Info(new Obj("I'm two"));
         return x;
     }
FYI those are thread local variables.
May 21 2016
prev sibling parent Kagamin <spam here.lot> writes:
On Saturday, 21 May 2016 at 10:42:13 UTC, chmike wrote:
     switch(x1)
     {
     case Infos.one: writeln("case Infos.one"); break;
     default: writeln("default"); break;
     }
You can generate fairly unique ids and use them in switch statements like this: https://dpaste.dzfl.pl/873b5b4cf71e
May 21 2016