跳转到内容

使用 Moose 编程/解决的问题/Scalar-Defer

来自维基教科书,开放世界中的开放书籍

Scalar::Deferlazy 是计算一个值一次并缓存它的一种方法,而 Memoize 是一种将函数的输入和输出存储到缓存表中以供将来调用的方法。 这两种功能在很大程度上由 Moose 的原生 lazy=>1, default=> 包含。 这种 Moose 的致命组合(lazydefault)会缓存函数的输出,并将执行延迟到第一个运行时请求。

从程序上讲,一个函数获取参数或对全局变量进行操作——如果它既不获取也不操作,那么它大概率是不可变[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

[编辑 | 编辑源代码]

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 的组合本质上做了一些事情
  1. 延迟执行到运行时
  2. 缓存变量以供以后调用
  3. 允许动态重载

因此,如果你不想使用 default/lazy 组合,只需向它发送一些东西。

参考文献

[编辑 | 编辑源代码]

Memoize v1.01:POD。 日期 2001-09-21。 访问日期 2007-10-21

  1. ^ 一个不可变函数意味着对于每个X,你都会得到相同的Y。 如果一个函数没有影响其输出(Y)的变量(X),那么所有函数实际上都是不可变的。 如果你必须思考这个问题,你可能应该从编程中退休。
  2. ^ 一个方法仅仅是对象命名空间中的一个函数。
  3. ^ 并不是所有东西都必须是面向对象的! 这不是 java; 我们在我们的语言中支持多种范式,并且我们知道这一点。
  4. ^ 因此,你可以通过调用 ->clearer 来违反这种不可变性,我们稍后会讨论这一点。
华夏公益教科书