Enforcing single instances from the metamodel
Solution 1:
There's a few misunderstandings in this attempt.
- There is one instance of a meta-class per type. Thus if we want to allow a given type to only be instantiated once, the correct scoping is an attribute in the meta-class, not a
my
. Amy
would mean there's one global object no matter which type we create. - The
compose
method, when subclassingClassHOW
, should always call back up to the basecompose
method (which can be done usingcallsame
). Otherwise, the class will not be composed. - The
method_table
method returns the table of methods for this exact type. However, most classes won't have anew
method. Rather, they will inherit the default one. If we wrap that, however, we'd be having a very global effect.
While new
is relatively common to override to change the interface to construction, the bless
method - which new
calls after doing any mapping work - is not something we'd expect language users to be overriding. So one way we could proceed is to just try installing a bless
method that does the required logic. (We could also work with new
, but really we'd need to check if there was one in this class, wrap it if so, and add a copy of the default one that we then wrap if not, which is a bit more effort.)
Here's a solution that works:
my class MetamodelX::Singleton is Metamodel::ClassHOW {
has $!instance;
method compose(Mu \type) {
self.add_method(type, 'bless', -> \SELF, |c {
without $!instance {
$!instance := SELF.Mu::bless(|c);
}
$!instance
});
callsame();
}
}
my package EXPORTHOW {
package DECLARE {
constant singleton = MetamodelX::Singleton;
}
}
Note that we can't use callsame
inside of the code we add for bless
because it's not actually a method
. We could instead write it to use an anon method
, but then we have the problem that the method has its own idea of self
, and so we end up having to save the meta-class self
and arrange some other means to access $!instance
.
Finally, an example of it in action:
use Singleton;
singleton Counter {
has $.x;
method inc() { $!x++ }
}
my $c1 = Counter.new;
$c1.inc;
my $c2 = Counter.new; # Gets same instance as in $c1
$c1.inc;
$c2.inc;
say $c1.x; # 3
say $c2.x; # 3