perlpragma - 如何撰寫使用者 pragma
pragma 是個模組,會影響 Perl 編譯時間或執行時間的某個面向,例如 strict
或 warnings
。從 Perl 5.10 開始,您不再受限於內建的 pragma;現在您可以建立使用者 pragma,在詞法範圍內修改使用者函式的行為。
例如,假設您需要建立一個實作重載數學運算子的類別,並希望提供一個自己的 pragma,其功能很像 use integer;
您希望這段程式碼
use MyMaths;
my $l = MyMaths->new(1.2);
my $r = MyMaths->new(3.4);
print "A: ", $l + $r, "\n";
use myint;
print "B: ", $l + $r, "\n";
{
no myint;
print "C: ", $l + $r, "\n";
}
print "D: ", $l + $r, "\n";
no myint;
print "E: ", $l + $r, "\n";
會產生以下輸出
A: 4.6
B: 4
C: 4.6
D: 4
E: 4.6
也就是說,當 use myint;
生效時,加法運算會強制轉換為整數,而預設情況下則不會,預設行為會透過 no myint;
還原
套件 MyMaths
的最小實作會類似這樣
package MyMaths;
use v5.36;
use myint();
use overload '+' => sub {
my ($l, $r) = @_;
# Pass 1 to check up one call level from here
if (myint::in_effect(1)) {
int($$l) + int($$r);
} else {
$$l + $$r;
}
};
sub new {
my ($class, $value) = @_;
bless \$value, $class;
}
1;
請注意我們如何使用空清單 ()
載入使用者實用程式 myint
,以防止呼叫其 import
。
與 Perl 編譯的互動發生在套件 myint
內部
package myint;
use v5.36;
sub import {
$^H{"myint/in_effect"} = 1;
}
sub unimport {
$^H{"myint/in_effect"} = 0;
}
sub in_effect {
my $level = shift // 0;
my $hinthash = (caller($level))[10];
return $hinthash->{"myint/in_effect"};
}
1;
由於實用程式是以模組實作,就像任何其他模組一樣,use myint;
會變成
BEGIN {
require myint;
myint->import();
}
而 no myint;
是
BEGIN {
require myint;
myint->unimport();
}
因此,import
和 unimport
常式會在使用者的程式碼中於編譯時間呼叫。
使用者實用程式會透過寫入神奇雜湊 %^H
來儲存其狀態,因此這兩個常式會操作它。%^H
中的狀態資訊會儲存在 optree 中,並可以在執行階段使用 caller()
以唯讀方式擷取,在傳回結果清單的索引 10 中。在範例實用程式中,擷取會封裝到常式 in_effect()
中,它會將呼叫架構的數量當作參數,以找出使用者的腳本中實用程式的值。這會使用 caller()
來判斷使用者腳本的每一行呼叫時 $^H{"myint/in_effect"}
的值,因此會在實作重載加法的子常式中提供正確的語意。
只有一個 %^H
,但有任意多個模組想要使用其範圍語意。為了避免互相踩到對方的腳,它們需要確定在雜湊中使用不同的金鑰。因此,模組慣例上只使用以模組名稱 (其主要套件的名稱) 和「/」字元開頭的金鑰。在此模組識別前綴之後,金鑰的其餘部分完全取決於模組:它可以包含任何字元。例如,模組 Foo::Bar
應該使用例如 Foo::Bar/baz
和 Foo::Bar/$%/_!
的金鑰。遵循此慣例的模組都彼此相安無事。
Perl 核心在 %^H
中使用少數金鑰,它們不遵循此慣例,因為它們早於此慣例。遵循慣例的金鑰不會與核心的歷史金鑰衝突。
optree 在執行緒之間共享。這表示 optree 有可能比建立它的特定執行緒(因此也比解釋器執行個體)存活更久,因此真正的 Perl 標量無法儲存在 optree 中。相反地,會使用一種精簡的格式,它只能儲存整數(有號和無號)、字串或 undef
的值 - 參照和浮點值會字串化。如果您需要儲存多個值或複雜的結構,您應該序列化它們,例如使用 pack
。從 %^H
刪除雜湊鍵會被記錄下來,而且一如往常,可以使用 exists
將它與值為 undef
的鍵的存在區分開來。
不要嘗試將參照儲存到資料結構中,作為透過 caller
擷取並轉換回來的整數,因為這不會執行緒安全。存取會在不鎖定的情況下進行(這對 Perl 的標量來說不安全),而且結構必須外洩,或者必須在建立它的執行緒終止時釋放它,這可能早於參照它的 optree 被刪除,如果其他執行緒比它存活更久的話。