使用 Moose 编程/解决的问题/Scalar-Defer
Scalar::Defer
的 lazy
是计算一个值一次并缓存它的一种方法,而 Memoize 是一种将函数的输入和输出存储到缓存表中以供将来调用的方法。 这两种功能在很大程度上由 Moose 的原生 lazy=>1, default=>
包含。 这种 Moose 的致命组合(lazy
和 default
)会缓存函数的输出,并将执行延迟到第一个运行时请求。
从程序上讲,一个函数获取参数或对全局变量进行操作——如果它既不获取也不操作,那么它大概率是不可变[1]。 从面向对象的角度来看,一个方法[2] 经常从对象(它的环境)中获取变量,有时会修改对象。 举个例子,像 ->shout
这样的方法,它可能会检查对象是否可以 shout()
,然后它可能会喊出对象正在 $self->thinking
什么。 考虑到这一点,让我们看看 Memoize 是怎么做的...
“记忆化”一个函数通过用空间换取时间来使它更快。 它通过在表中缓存函数的返回值来实现这一点。 如果你用相同的参数再次调用函数,“记忆化”就会介入并从表中给你返回值,而不是让函数再次计算值。
你可能会问,这有什么问题? 首先,它不是面向对象的[3]; 而且,对方法引用做任何事情都是丑陋且讨厌的,应该避免。 Moose 的 Lazy/Default 组合替换了 Memoize 功能的一个小而重要的子集。 如果你只是想缓存一个运行时函数的返回值,那么 Moose 的原生功能可能更适合你。
最后,Memoize 的目的始终是让某些东西更快,而这与 Moose 的目标在任何方面都不一致。 Moose 通过 lazy
/default
提供的功能是出于完全不同的原因,所有这些都与程序员的生产力有关,而这正是 Moose 的目标。 Moose 的这种方法的逻辑缺点是,对象只会缓存对函数的一次调用,需要许多对象才能进行许多调用。
至于 Scalar::Defer
,这可能更符合 Moose 提供的功能,因为 Scalar::Defer
的目标直接与 Moose 的 Lazy/Default 的目标冲突。 让我们用一个列表总结 Lazy/Default 组合可以做的事情。
- 只计算一次函数
- 缓存输出
- 延迟评估到第一次调用
我(Evan Carroll)以前从未在方法上见过使用记忆化。 这并不是说它不能完成,或者没有完成过……但,我不会为了让 Moose 更胜一筹而编造一个例子,我会向你展示一个典型的原始 Memoize。 也就是说,理论上可以把它做得更相似,但实际上不行。
这个例子将使用 lazy/default 来延迟执行和存储结果; 它经常仅仅出于这个目的而使用。 但是,它也经常用于将数据库连接延迟到运行时才需要。 这些是同一技术的两个完全不同的应用。
use Memoize;
memoize( 'complex_operation' );
sub complex_operation {
my $num = shift;
my $result = $num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
;
}
print complex_operation( 1 );
Moose 将以一种略微不同——面向对象——的方式获得相同的效果。
package MyMoose;
use Moose;
has 'foo_var' => (
isa => 'Int'
, is => 'ro'
);
has 'foo' => (
isa => 'Int'
, is => 'rw'
, lazy => 1
, default => \&build_foo
);
sub build_foo {
my $self = shift;
my $num = $self->foo_var;
my $result = $num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
+$num+$num+$num+$num+$num+$num+$num
;
}
package main;
my $m = MyMoose->new({ foo_var => 1 });
print $m->foo;
我觉得有义务向你展示这项技术的非常流行的应用。
package MyApp::DB;
use Moose;
use DBI;
has 'dbh' => (
isa => 'Object'
, is => 'ro'
, lazy => 1
, default => \&build_dbh
);
sub build_dbh {
my $self = shift;
my $dbh = DBI->connect(
'dbi:Pg:dbname=myDB;host=localhost'
, 'username'
, 'password'
) or die "Can not connect to DB $DBI::errstr";
$dbh;
}
为了清晰起见,以及那些无法从例子中学习的人
- lazy
- Lazy 可以简化为:在运行时做这件事。 它允许延迟执行。
- default
- Default 总是与lazy结合使用,告诉 Moose 第一次调用 getter 时应该发生什么。 lazy/default 的组合本质上做了一些事情
- 延迟执行到运行时
- 缓存变量以供以后调用
- 允许动态重载
因此,如果你不想使用 default/lazy 组合,只需向它发送一些东西。
Memoize v1.01:POD。 日期 2001-09-21。 访问日期 2007-10-21