簽名也是對象
> sub a($a, $b) {};
> &a.signature.perl.say
:($a, $b)
> my $b = -> $a, $b {};
> $b.signature.perl.say
:($a, $b)
簽名是一個對象, 就像 Perl 6 中的任何其它東西一樣访忿。 任何 Callable 類型中都有簽名, 并且它能使用 .signature
方法獲取到。
class Signature { ... }
簽名是代碼對象參數(shù)列表的靜態(tài)描述霎褐。即, 簽名描述了你需要什么參數(shù)和多少參數(shù)傳遞給代碼或函數(shù)以調(diào)用它們。
傳遞參數(shù)給簽名把包含在 Capture 中的參數(shù)綁定到了簽名上。
簽名字面量
簽名出現(xiàn)在子例程和方法名后面的圓括號中, 還出現(xiàn)在 blocks 里面的 ->
或 <->
后面, 或者作為變量聲明符(例如 my
)的輸入, 或者以冒號開頭作為單獨的項。
sub f($x) { }
# ^^^^ sub f 的簽名
method x() { }
# ^^ 方法 x 的簽名
my $s = sub (*@a) { }
# ^^^^^ 匿名函數(shù)的簽名
for @list -> $x { }
# ^^ block 的簽名
my ($a, @b) = 5, (6,7,8);
# ^^^^^^^^ 變量聲明符的簽名
my $sig = :($a, $b);
# ^^^^^^^^ 獨立的簽名對象
簽名字面量可以用于定義回調(diào)或閉包的簽名县踢。
sub f(&c:(Int)){}
sub will-work(Int){}
sub won't-work(Str){}
f(&will-work);
f(&won't-work); # fails at runtime
f(-> Int { 'this works too' } );
參數(shù)分隔符
簽名由逗號分割的0個或多個參數(shù)組成。
:($a, @b, %c)
sub add ($a, $b) { $a + $b }
作為一個例外, 簽名中的第一個參數(shù)后面可以跟著一個冒號而非逗號來標記方法的調(diào)用者伟件。調(diào)用者是用于調(diào)用方法的東西, 它通常通過在簽名中指定它來綁定給 self, 你可以更改所綁定的變量的名字硼啤。
:($a: @b, %c) # 第一個參數(shù)是調(diào)用者
class Foo {
method whoami ($me:) {
"Well I'm class $me.^name(), of course!"
}
}
say Foo.whoami; # Well I'm class Foo, of course!
類型約束
參數(shù)可以可選地擁有一個類型約束(默認為 Any
)。這些能用于限制函數(shù)允許的輸入斧账。
:(Int $a, Str $b)
sub divisors (Int $n) { $_ if $n %% $_ for 1..$n }
divisors 2.5; # !!! Calling 'divisors' will never work with argument types (Rat)
匿名的參數(shù)也行, 如果參數(shù)只需要它的類型約束的話谴返。
:($, @, %a) # 兩個匿名參數(shù)和一個 "正常的(有名字的)"參數(shù)
:(Int, Positional) # 只有類型也行(兩個參數(shù))
sub baz (Str) {"Got a String"}
baz("hello");
類型約束也可以是類型捕獲(type captures)。
除了這些名義上的類型之外, 額外的約束可以以代碼塊的形式加到參數(shù)上, 代碼塊必須返回一個真值以通過類型檢測咧织。
sub f(Real $x where { $x > 0 }, Real $y where { $y >= $x }) { }
事實上, where 后面不需要是一個代碼塊, where
-block右側(cè)的任何東西都會被用于和參數(shù)智能匹配亏镰。所以你也可以這樣寫:
multi factorial(Int $ where 0) { 1 }
multi factorial(Int $x) { $x * factorial($x - 1) }
第一個還能簡化為
multi factorial(0) { 1 }
你可以直接把字面量用作類型而值約束到匿名參數(shù)上。
約束定義值和未定義值
通常, 類型約束只檢查傳遞的值是否是正確的類型拯爽。
sub limit-lines (Str $s, Int $limit) {
my @lines = $s.lines;
@lines[0 ..^ min @lines.elems, $limit].join("\n")
}
say (limit-lines "a \n b \n c \n d \n", 3).perl; # "a \n b \n c "
say limit-lines Str, 3; # Uh-oh. Dies with "Cannot call 'lines';"
say limit-lines "a \n b", Int # Always returns the max number of lines
這樣的情況, 我們其實只想處理定義了的字符串。要這樣做, 我們使用 :D
類型約束钧忽。
sub limit-lines (Str:D $s, Int $limit) {
...
}
say limit-lines Str, 3;
# Dies with "參數(shù) '$s' 需要一個實例, 但是函數(shù) limit-lines 中卻傳遞了一個類型對象毯炮。
如果傳遞一個諸如 Str 這樣的類型對象進去, 那么就會報錯。這樣的失敗方式比以前更好了, 因為失敗的原因更清晰了耸黑。
也有可能未定義的類型是子例程唯一有意義的接收值桃煎。這可以使用 :U
類型約束來約束它。例如, 我們可以把 &limit-lines
轉(zhuǎn)換成 multi 函數(shù)以使用 :U
約束大刊。
multi limit-lines (Str $s, Int:D $limit) {
my @lines = $s.lines;
@lines[0 ..^ min @lines.elems, $limit].join("\n");
}
multi limit-lines (Str $s, Int:U $) {$s} # 如果傳遞給我一個未定義的類型對象, 就返回整個字符串
say limit-lines "a \n b \n c", Int; # "a \n b \n c"
為了顯式地標示常規(guī)的行為, 可以使用:_
, 但這不是必須的为迈。 :(Num:_ $)
和 Num $
相同。
約束返回類型
-->
標記后面跟著一個類型會強制在子例程執(zhí)行成功時進行類型檢測缺菌。返回類型箭頭必須放在參數(shù)列表的后面葫辐。跟在簽名聲明后面的 returns
關(guān)鍵字有同樣的功能。Nil
在類型檢測中被認為是定義了的伴郁。
sub foo(--> Int) { 1 };
sub foo() returns Int { 1 }; # 同上
sub does-not-work(--> Int) { " " }; # throws X::TypeCheck::Return
如果類型約束是一個常量表達式, 那么它被用于子例程的返回值耿战。那個子例程中的任何return語句必須是不含參數(shù)的。
sub foo(--> 123) { return }
Nil
和 Failure
總是被允許作為返回類型, 不管類型約束是什么焊傅。
sub foo(--> Int) { Nil };
say foo.perl; # Nil
不支持類型捕獲和強制類型剂陡。
吞噬參數(shù)(或長度可變參數(shù))
數(shù)組或散列參數(shù)可以通過前置一個星號(s)被標記為吞噬參數(shù), 這意味著它可以被綁定給任意數(shù)量的參數(shù)(0 個或 多個)。
它們被叫做吞噬參數(shù), 因為它們吞完函數(shù)中的任何剩余參數(shù), 就像有些人吞吃面條那樣狐胎。
:($a, @b) # 正好兩個參數(shù), 而第二個參數(shù)必須是 Positional 的
:($a, *@b) # 至少一個參數(shù), @b 吞噬完任何剩余的參數(shù)
:(*%h) # 沒有位置參數(shù), 除了任意數(shù)量的具名參數(shù)
sub one-arg (@) { };
sub slury (*@) { };
one-arg(5, 6, 7); # !!! 參數(shù)個數(shù)太多
one-arg (5, 6, 7); # ok, 和 one-arg((5,6,7))相同, 傳遞的是一個數(shù)組
slurp (5, 6, 7); # ok
one-arg 5, 6, 7; # 調(diào)用 one-arg(Int, Int, Int) 絕對不會工作, 使用聲明的簽名 (@), 參數(shù)個數(shù)太多
slurp 5, 6, 7; # ok
one-arg (5); # Calling one-arg(Int) will never work with declared signature (@)
one-arg (5,); # ok
one-arg 函數(shù)需要的參數(shù)是一個列表(或數(shù)組), 而不是多個參數(shù)鸭栖。
> (5).WHAT.say
(Int)
> (5,).WHAT.say
(List)
sub named-names (*%named-args) { %named-args.keys };
say named-names :foo(42) :bar<hahaha> # => foo bar
注意位置參數(shù)不允許出現(xiàn)在吞噬參數(shù)的后面:
:(*@args, $last) # !!! 不能把必要參數(shù)放在可變長度參數(shù)的后面
帶有一個星號的吞噬參數(shù)會通過消融一層或多層裸的可迭代對象來展平參數(shù)。 帶有兩個星號的吞噬參數(shù)不會展平參數(shù):
sub a (*@a) { @a.join("|").say };
sub b (**@b) { @b.join("|").say };
a(1,[1,2],([3,4],5)); # 1|1|2|3|4|5
b(1,[1,2],([3,4],5)); # 1|1 2|3 4 5
通常, 吞噬參數(shù)會創(chuàng)建一個數(shù)組, 為每個 argument 創(chuàng)建一個標量容器, 并且把每個參數(shù)的值賦值給那些標量握巢。如果在該過程中原參數(shù)也有一個中間的標量分量, 那么它在調(diào)用函數(shù)中是訪問不到的晕鹊。
吞噬參數(shù)在和某些traits and modifiers組合使用時會有特殊行為, 像下面描述的那樣。
類型捕獲
類型捕獲允許把類型約束的說明推遲到函數(shù)被調(diào)用時。它們允許簽名和函數(shù)體中的類型都可以引用捏题。
sub f(::T $p1, T $p2, ::C) {
# $p1 和 $p2 的類型都為 T, 但是我們還不知道具體類型是什么
# C 將會保存一個源于類型對象或值的類型
my C $closure = $p1 / $p2;
return sub (T $p1) {
$closure * $p1;
}
}
# 第一個參數(shù)是 Int 類型, 所以第二個參數(shù)也是
# 我們從調(diào)用用于 &f 中的操作符導出第三個類型
my &s = f(10,2, Int.new / Int.new);
say s(2); # 10 / 2 * 2 == 10
Positional vs. Named
參數(shù)可以是跟位置有關(guān)的或者是具名的玻褪。所有的參數(shù)都是 positional 的, 除了吞噬型散列參數(shù)和有前置冒號標記的參數(shù):
:($a) # 位置參數(shù)
:(:$a) # 名字為 a 的具名參數(shù)
:(*@a) # 吞噬型位置參數(shù)
:(*%h) # 吞噬型具名參數(shù)
在調(diào)用者這邊, 位置參數(shù)的傳遞順序和它們聲明順序相同。
sub pos($x, $y) { "x = $x y = $y" };
pos(4, 5); # x = 4 y = 5
對于具名實參和具名形參, 只用名字用于將實參映射到形參上公荧。
sub named(:$x, :$y) { "x=$x y=$y" }
named( y => 5, x => 4);
具名參數(shù)也可以和變量的名字不同:
sub named(:official($private)) { "公務" if $private }
named :official;
別名也是那樣做的:
sub paint( :color(:colour($c)) ) { } # 'color' 和 'colour' 都可以
sub paint( :color(:$colour) ) { } # same API for the caller
帶有具名參數(shù)的函數(shù)可以被動態(tài)地調(diào)用, 使用 |
非關(guān)聯(lián)化一個 Pair 來把它轉(zhuǎn)換為一個具名參數(shù)带射。
multi f(:$named) { note &?ROUTINE.signature };
multi f(:$also-named) { note &?ROUTINE.signature };
for 'named', 'also-named' -> $n {
f(|($n => rand)) # ?(:$named)
(:$also-named)
?
}
my $pair = :named(1);
f |$pair; # ?(:$named)
?
同樣的語法也可以用于將散列轉(zhuǎn)換為具名參數(shù):
my %pairs = also-named => 4;
f |%pairs; # (:$also-named)
可選參數(shù)和強制參數(shù)
Positional 參數(shù)默認是強制的, 也可以用默認值或結(jié)尾的問號使參數(shù)成為可選的:
:(Str $id) # 必要參數(shù) required parameter
:($base = 10) # 可選參數(shù), 默認為 10
:(Int $x?) # 可選參數(shù), 默認為 Int 類型的對象
具名參數(shù)默認是可選的, 可以通過在參數(shù)末尾加上一個感嘆號使它變成強制參數(shù):
:(:%config) # 可選參數(shù)
:(:$debug = False) # 可選參數(shù), 默認為 False
:(:$name!) # 名為 name 的強制具名參數(shù)
默認值可以依靠之前的參數(shù), 并且每次調(diào)用都會被重新計算。
:($goal, $accuracy = $goal / 100);
:(:$excludes = ['.', '..']); # a new Array for every call
解構(gòu)參數(shù)
參數(shù)后面可以跟著一個由括號括起來的 sub-signature
, 子簽名會解構(gòu)給定的參數(shù)循狰。解構(gòu)的列表就是它的元素:
sub first (@array ($first, *@rest)) { $first }
或
sub first ([$first, *@]) { $first }
而散列的解構(gòu)是它的鍵值對兒:
sub all-dimensions (% (:length(:$x), :width(:$y), :depth(:$z))) {
sx andthen $y andthen $z andthen True
}
andthen
返回第一個未定義的值, 否則返回最后一個元素窟社。短路操作符。andthen
左側(cè)的結(jié)果被綁定給 $_
用于右側(cè), 或者作為參數(shù)傳遞, 如果右側(cè)是一個 block
或 pointy block
的話绪钥。
一般地, 對象根據(jù)它的屬性結(jié)構(gòu)灿里。通用的慣用法是在 for 循環(huán)中解包一個 Pair
的鍵和值:
for @guest-list.pairs -> (:key($index), :value($guest)) {
...
}
然而, 這種把對象解包為它們的屬性只是默認行為。為了讓對象按照不同的方解構(gòu), 改變它們的 Capture
方法程腹。
捕獲參數(shù)
在參數(shù)前前置一個垂直的 |
會讓參數(shù)變?yōu)?Capture
, 并使用完所有剩下的位置參數(shù)和具名參數(shù)匣吊。
這常用在 proto
定義中( 像 proto foo (|) {*}
) 來標示子例程的 multi
定義可以擁有任何類型約束。
參數(shù)特性和修飾符
默認地, 形式參數(shù)被綁定到它們的實參上并且被標記為只讀寸潦。你可以使用 traits 特性更改參數(shù)的只讀特性色鸳。
is copy
特性讓參數(shù)被復制, 并允許在子例程內(nèi)部修改參數(shù)的值。
sub count-up ($x is copy) {
$x = Inf if $x ~~ Whatever;
.say for 1..$x;
}
is rw
特性讓參數(shù)只綁定到變量上(或其它可寫的容器)见转。 賦值給參數(shù)會改變調(diào)用一側(cè)的變量的值命雀。
sub swap($x is rw, $y is rw) {
($x, $y) = ($y, $x);
}
對于吞噬參數(shù), is rw
由語言設(shè)計者保留做將來之用
方法
params 方法
method params(Signature:D:) returns Positional
返回 Parameter
對象列表以組成簽名。
arity 方法
method arity(Signature:D:) returns Int:D
返回所必須的最小數(shù)量的滿足簽名的位置參數(shù)
count 方法
method count(Signature:D:) returns Real:D
返回能被綁定給簽名的最大數(shù)量的位置參數(shù)斩箫。如果有吞噬位置參數(shù)則返回 Inf
吏砂。
returns 方法
簽名返回的任意約束是:
:($a, $b --> Int).returns # Int
ACCEPTS 方法
multi method ACCEPTS(Signature:D: Capture $topic)
multi method ACCEPTS(Signature:D: @topic)
multi method ACCEPTS(Signature:D: %topic)
multi method ACCEPTS(Signature:D: Signature $topic)
前三個方法會看參能否綁定給 capture, 例如, 如果帶有那個 Signature 的函數(shù)能使用 $topic
調(diào)用:
(1,2, :foo) ~~ :($a, $b, :foo($bar)) # true
<a b c d> ~~ :(Int $a) # False
最后一個會為真如果 $topic
能接收的任何東西也能被 Signature
接收。
:($a, $b) ~~ :($foo, $bar, $baz?) # True
:(Int $n) ~~ :(Str) # False