Perl 6 中的函數(shù)簽名

簽名也是對象


> 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 }

NilFailure總是被允許作為返回類型, 不管類型約束是什么焊傅。

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è)是一個 blockpointy 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乘客,一起剝皮案震驚了整個濱河市狐血,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌易核,老刑警劉巖氛雪,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耸成,居然都是意外死亡报亩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門井氢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弦追,“玉大人,你說我怎么就攤上這事花竞【⒓” “怎么了掸哑?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長零远。 經(jīng)常有香客問我苗分,道長,這世上最難降的妖魔是什么牵辣? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任摔癣,我火速辦了婚禮,結(jié)果婚禮上纬向,老公的妹妹穿的比我還像新娘择浊。我一直安慰自己,他們只是感情好逾条,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布琢岩。 她就那樣靜靜地躺著,像睡著了一般师脂。 火紅的嫁衣襯著肌膚如雪担孔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天吃警,我揣著相機與錄音糕篇,去河邊找鬼。 笑死汤徽,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的灸撰。 我是一名探鬼主播谒府,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浮毯!你這毒婦竟也來了完疫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤债蓝,失蹤者是張志新(化名)和其女友劉穎壳鹤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饰迹,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡芳誓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啊鸭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锹淌。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赠制,靈堂內(nèi)的尸體忽然破棺而出赂摆,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布烟号,位于F島的核電站绊谭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汪拥。R本人自食惡果不足惜达传,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喷楣。 院中可真熱鬧趟大,春花似錦、人聲如沸铣焊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曲伊。三九已至叽讳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坟募,已是汗流浹背岛蚤。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留懈糯,地道東北人涤妒。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像赚哗,于是被迫代替她去往敵國和親她紫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容