跳轉到內容

使用 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 來違反這種不可變性,我們稍後會討論這一點。
華夏公益教科書