从元模型强制执行单个实例

jjm*_*elo 5 metaprogramming rakudo raku

我知道从类级别确保单个实例要容易得多,并且StaticishJonathan Stowe 提供了一个出色的模块,它通过使用角色来执行相同的操作,但我只是想尝试更好地理解类的高阶工作是如何进行的被处理,主要是为了 FOSDEM 演讲。我可以想到在元模型级别上执行的几种方法,但最终我想到的是:

my class MetamodelX::Singleton is Metamodel::ClassHOW {

    my \instance = Mu;

    method compose(Mu \type) {
        my &callsame := CORE::<&callsame>; # Workaround for RT #127858
        self.method_table(type)<new>.wrap: -> \SELF, | {
            unless instance.defined { instance = SELF };
            callsame();
        };
    }
}

my package EXPORTHOW {
    package DECLARE {
        constant singleton = MetamodelX::Singleton;
    }
}
Run Code Online (Sandbox Code Playgroud)

主要是从OO::Monitors代码中摘录的,据我所知,由 JJ Atria 和 Jonathan Worthington 编写)

主要原理是尝试包装构建子方法,或者以某种方式尝试创建对象的新实例。然而,这(以及与BUILDand相同BUILDALL,接近原始)失败了:

No such method 'wrap' for invocant of type 'Any'.  Did you mean any of
these: 'Map', 'WHAT', 'grep', 'map'?
Run Code Online (Sandbox Code Playgroud)

很明显,我不明白它们的作用,或者整个“如何”概念。那么,你知道这里可能会失败吗,或者有任何其他方法可以在元模型级别覆盖对象的构建,以便能够按预期进行吗?

Jon*_*ton 6

这次尝试存在一些误解。

  1. 每种类型都有一个元类实例。因此,如果我们希望允许给定类型仅实例化一次,则正确的作用域是元类中的属性,而不是my. Amy意味着无论我们创建哪种类型,都有一个全局对象。
  2. compose方法在子类化时ClassHOW,应始终回调到基compose方法(可以使用 来完成callsame)。否则,班级将无法组成。
  3. 该方法返回该确切method_table类型的方法表。但是,大多数类没有方法。相反,他们将继承默认值。然而,如果我们把包装起来,我们就会产生非常全球性的影响。new

虽然new重写以更改构造接口的情况相对常见,但该bless方法(new在完成任何映射工作后调用)并不是我们期望语言用户重写的方法。因此,我们可以继续的一种方法是尝试安装一个bless执行所需逻辑的方法。(我们也可以使用new,但实际上我们需要检查此类中是否有一个,如果有则将其包装,然后添加默认副本的副本,如果没有则将其包装,这需要更多的努力。 )

这是一个有效的解决方案:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们不能callsame在添加的代码内部使用bless,因为它实际上不是method. 我们可以改为使用 an 来编写它anon method,但是我们会遇到一个问题,即该方法有自己的想法self,因此我们最终不得不保存元类self并安排一些其他方式来访问$!instance

最后是一个实际的例子:

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
Run Code Online (Sandbox Code Playgroud)

  • @jjmerelo 不,从来没有生成一个“bless”,所有东西都继承了“Mu”中的“bless”——除非它定义了一个,而我几乎从未见过。您可能会想到生成的“BUILDALL”。 (2认同)