1 回答

TA貢獻1877條經驗 獲得超6個贊
Perl 可以通過函數元型在編譯期進行有限的參數類型檢驗。如果你聲明
sub mypush (+@)
那么 mypush() 對參數的處理就同內置的 push() 完全一樣了。函數聲明必須要在編譯
相應函數調用之前告知編譯器(編譯器在編譯函數調用時會對相應函數用 prototype
來查詢它的元型來進行參數檢驗,并決定怎樣編譯此函數調用)。元型只在不用 & 調用
函數的時候起作用。就是說在語法上如果你想像內置函數一樣調用,它就表現的像
內置函數一樣。如果想用過時的風格通過 & 調用,那么編譯器就無視函數聲明。另外
元型在函數引用如 \&foo 和間接調用如 &{$subref} 和 $subref->() 時也不起作用。
方法調用也不受元型影響,因為實際調用的函數無法在編譯期決定,它是由繼承關系
決定的。
因為這個特性最初的目的是使你可以像內置函數那樣調用自己的函數,所以下面就給出
等價于內置函數調用方式的函數元型。
聲明為 調用方式
sub mylink ($$) mylink $old, $new
sub myvec ($$$) myvec $var, $offset, 1
sub myindex ($$;$) myindex &getstring, "substr"
sub mysyswrite ($$$;$) mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (@) myreverse $a, $b, $c
sub myjoin ($@) myjoin ':', $a, $b, $c
sub mypop (+) mypop @array
sub mysplice (+$$@) mysplice @array, 0, 2, @pushme
sub mykeys (+) mykeys %{$hashref}
sub myopen (*;$) myopen HANDLE, $name
sub mypipe (**) mypipe READHANDLE, WRITEHANDLE
sub mygrep (&@) mygrep { /foo/ } $a, $b, $c
sub myrand (;$) myrand 42
sub mytime () mytime
任何 \ 跟著的函數元型中的字符代表著實際的參數必須由相應字符開頭(參數前可跟
my our local 聲明),只有 $ 例外,它可以接收并不以 $ 開頭的 hash 和數組的元素,
比如 my_function()->[0]。傳給 @_ 的參數將會是相應實際參數的引用,即對它加 \。
你可以用 \[] 來表示多個可用的類型。比如:
sub myref (\[$@%&*])
上面的函數聲明允許像下面這樣調用 myref() 這個函數
myref $var
myref @array
myref %hash
myref &sub
myref *glob
傳入函數 myref 的第一個參數將分別是一個 scalar、數組、hash、函數、glob 的引用。
函數元型中前面不跟 \ 的字符有特殊意義。任何不跟 \ 的 @ % 將代表剩下的所有參數,
并提供 list context。而 $ 將提供 scalar context。 & 表示需要一個匿名函數(即
sub { } 這樣的結構,不能是變量),當用作第一個參數時可以省掉 sub 關鍵字(如果
省掉 sub 則后面跟的逗號也必須要省掉).
* 表明可以接收一個 bareword、常量、scalar 表達式、typeglob或 typeglob 的引用。
傳入函數的參數要么是一個簡單的 scalar 要么是 typeglob 的引用(后兩種情況)。如果
你總是想要一個 typeglob 的引用可以用 Symbol::qualify_to_ref() 將名字轉換成相應
的 typeglob 的引用:
use symbol 'qualify_to_ref';
sub foo (*) {
my $fh = qualify_to_ref(shift, caller);
...
}
+ 類似于 $ 但是當遇到數組變量或 hash 變量時表示 \[@%],在其它情況下總是提供
scalar context。它適用于可以接收數組變量或數組引用為參數的函數:
sub mypush (+@) { # 5.14 中 push 第一個參數可以為數組的引用
my $aref = shift;
die "Not an arrayref" unless ref $aref eq 'ARRAY';
push @$aref, @_;
}
當用 + 時函數必須要檢驗實際的參數是否是自己需要的類型,因為它不區分 @ %。
分號 ; 用來分隔必須的參數和可選的參數。它必須在 @ % 之前,因為它們代表剩下的
所有參數。
在元型最后或在 ; 之前可以用 _ 來代替 $:它表示如果沒有提供這個參數會傳遞 $_
作為對應的參數,它可以用來實現默認參數的語法。
注意上面列表最后3個例子,mygrep() 表現的就像列表操作符,myrand() 表現的就像
rand() 一樣為一元操作符,mytime() 就像 time() 一樣完全不需要參數。如果你這么用:
mytime + 2;
你將會得到 mytime() + 2,而不是 mytime(2),沒有函數元型根本無法實現這樣的效果。
有意思的是你可以把 & 用在最開始的位置來創造新語法:
sub try (&@) {
my ($try, $catch) = @_;
eval { &$try };
if ($@) {
local $_ = $@;
&$catch;
}
}
sub catch (&) { $_[0] }
try {
die "phooey";
} catch {
/phooey/ and print "unphooey\n";
};
上面的代碼會打印 "unphooey",即是 Try::Tiny 的實現方法。(當然用 &$catch 會
將 @_ 暴露給 $catch 但這里并不是我們要考慮的)。
讓我們重新實現下 Perl 的 grep 操作符:
sub mygrep (&@) { # 無法實現 grep EXPR,LIST 這個語法
my $code = shift;
my @result;
foreach $_ (@_) {
push @result, $_ if &$code;
}
@result;
}
請不在要函數元型中使用字母或數字,它們被保留作它用,或許在將來用于實現完整的
參數列表。不要為老的代碼添加上函數元型,因為有時會改變語意出來奇怪的結果。比如:
sub func ($) {
my $n = shift;
print "you ave me $n\n";
}
某人在代碼中這么調用它:
func(@foo);
func(split /:/);
只是聲明了函數 func 只接收一個 scalar 參數卻帶來了災難性的結果,原來參數所處的
list context 被改為 scalar context,傳入的參數變成 @foo 的元素個數,和分割的
元素個數。
元型很強大也很危險,請小心慎用。
添加回答
舉報