2

Can anyone provide a code example how do you set watchers on variable change inside of class ? I tried to do it several ways using different features (Scalar::Watcher, trigger attribute of Moo) and OOP frameworks (Moo, Mojo::Base) and but all failed.

Below is my failed code for better understanding of my task. In this example i need to update attr2 everytime when attr1 changed.

Using Mojo::Base and Scalar::Watcher:

package Cat;
use Mojo::Base -base;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => 1;
has 'attr2' => 2;

has 'test' => sub { # "fake" attribute for getting access to $self
  my $self = shift;
  when_modified $self->attr1, sub { $self->attr2(3); say "meow" };
};


package main;
use Data::Dumper;

my $me = Cat->new;
$me->attr1;
warn Dumper $me;
say $me->attr1(3)->attr2; # attr2 is still 2, but must be 3

Using Moo and trigger:

package Cat;
use Moo;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => ( is => 'rw', default => 1, trigger => &update() ); 
has 'attr2' => ( is => 'rw', default => 1);

sub update {
  my $self = shift;
  when_modified $self->attr1, sub { $self->attr2(3); say "meow" }; # got error here: Can't call method "attr1" on an undefined value
};


package main;
use Data::Dumper;

my $me = Cat->new;
$me->attr1;
warn Dumper $me;
say $me->attr1(3)->attr2;

Any suggestion is much appreciated.

4

1 回答 1

5

Moo部分

此处出现错误:无法在未定义的值上调用方法“attr1”

这是因为 Moo 期望代码引用为triggerfor has。您正在将调用的结果传递给update. 这里&没有给你参考,而是告诉 Perl 忽略update函数的原型。你不想要那个。

相反,创建一个引用\&foo而不添加括号()。您不想调用该函数,而是要引用它。

has 'attr1' => ( is => 'rw', default => 1, trigger => \&update );

现在,一旦你这样做了,你就不再需要 Scalar::Watcher 了。触发器已经这样做了。每次attr1更改时都会调用它。

sub update {
    my $self = shift;
    $self->attr2(3);
    say "meow";
};

如果你现在运行整个程序,它会工作一点,但会因以下错误而崩溃:

无法通过包“3”定位对象方法“attr2”(也许您忘记加载“3”?)

那是因为attr1返回新值,而不是对$self. 所有 Moo/Moose 访问器都是这样工作的。而且3不是对象,所以它没有方法attr2

#       this returns 1
#               |
#               V
say $me->attr1(3)->attr2;

相反,将其作为两个调用来执行。

$me->attr1(3);
say $me->attr2;

这是一个完整的例子。

package Cat;
use Moo;

use feature 'say';

has 'attr1' => ( is => 'rw', default => 1, trigger => \&update );
has 'attr2' => ( is => 'rw', default => 1 );

sub update {
    my $self = shift;
    $self->attr2(3);
    say "meow";
}

package main;
my $me = Cat->new;

say $me->attr2;
$me->attr1(3);
say $me->attr2;

和输出:

1
meow
3

为什么 Scalar::Watcher 不适用于 Mojo

首先,Mojo::Base 没有提供触发机制。但是您实现 Scalar::Watcher 的方式无法工作,因为该test方法从未被调用过。我尝试new在基于 Mojo::Base 的类中进行when_modified调用,以便在始终调用它的地方进行调用。

从这里开始的一切都只是猜测。

以下片段是我尝试过的,但它不起作用。我将在下面进一步解释原因。

package Cat;
use Mojo::Base -base;
use Scalar::Watcher qw(when_modified);
use feature 'say';

has 'attr1' => '1';
has 'attr2' => 'original';

sub new {
    my $class = shift;

    my $self = $class->SUPER::new(@_);
    when_modified $self->{attr1}, sub { $self->attr2('updated'); say "meow" };

    return $self;
}

如您所见,这现在是new调用的一部分。代码确实被执行。但这无济于事。

Scalar::Watcher的文档指出,观察者应该在那里,直到变量超出范围。

如果在 void 上下文中调用 when_modified,则观察者将一直处于活动状态,直到 $variable 的生命结束;否则,它将返回对取消程序的引用,以在取消程序被垃圾收集时取消此观察程序。

但我们实际上没有标量变量。如果我们尝试做

when_modified $self->foo

foo然后 Perl 执行on方法调用$selfwhen_modified获得该调用的返回值。我也尝试进入上面对象的内部,但这也不起作用。

我的 XS 不够强大,无法理解这里发生了什么,但我认为它在附加这种魔法时遇到了一些麻烦。它不能与哈希参考值一起使用。可能这就是它被称为 Scalar::Watch 的原因。

于 2017-04-10T15:14:34.543 回答