Perl 6 的一個很好的特性是 multi-dispatch, 即多重分派迁匠。它允許你在函數(shù), 方法或Grammar token 中使用相同的名字并讓它們所處理的數(shù)據(jù)的類型來決定執(zhí)行哪一個。下面是一個 factorial postfix 操作符, 用兩個 multies 來實現(xiàn):
multi postfix:<!> (0) { 1 }
multi postfix:<!> (UInt \n) { n × samewith n ? 1 }
say 5!
# OUTPUT: 120
雖然 multi-dispatch 的主題很明顯并且它還有一些說明文檔, 我今天想講的是 7 個特殊的子例程, 讓你能夠行走在 dispatch 迷宮中。它們是 nextwith
, nextsame
, samewith
, callwith
, callsame
, nextcallee
和 lastcall
.
設(shè)立實驗室
Multies 從最窄到最廣的候選者進行排序俏拱,并且當(dāng)一個 multi 被調(diào)用時埃篓,綁定器嘗試找到一個匹配并調(diào)用第一個匹配的候選者雷恃。 有時疆股,您可能希望調(diào)用或簡單地移動到鏈中的下一個匹配候選者费坊,可選地使用不同的參數(shù)倒槐。 為了觀察這些操作的效果,我們將使用以下設(shè)置:
class Wide { }
class Middle is Wide { }
class Narrow is Middle { }
multi foo (Narrow $v) { say 'Narrow ', $v; 'from Narrow' }
multi foo (Middle $v) { say 'Middle ', $v; 'from Middle' }
multi foo (Wide $v) { say 'Wide ', $v; 'from Wide' }
foo Narrow; # OUTPUT: Narrow (Narrow)
foo Middle; # OUTPUT: Middle (Middle)
foo Wide; # OUTPUT: Wide (Wide)
我們有三個類附井,每個類都繼承自前一個類讨越,所以我們的 Narrow
類 可以適應(yīng) Middle
和 Wide
multi 候選者; Middle
也可以適應(yīng) Wide
,但不能適應(yīng) Narrow
; 而 Narrow
既不適用于 Middle
永毅,也不適用于 Narrow
把跨。請記住,Perl 6 中的所有類也都是 Any 類型沼死,因此也適用于任何接受 Any 的候選者着逐。
對于我們的 Callables,我們在 foo 子例程上使用三個 multi 候選者:每個類一個意蛀。在類的主體中耸别,我們打印什么我們所調(diào)用的 multi 的類型,以及作為參數(shù)傳遞的值县钥。對于它們的返回值秀姐,我們只使用一個字符串來告訴我們返回值來自哪個 multi;我們稍后會使用這些。
最后若贮,我們使用三個類型的對象與我們的自定義類進行三次調(diào)用省有。從輸出來看,我們可以看到三個候選者中的每一個候選者都按預(yù)期被調(diào)用了谴麦。
這一切都是平淡而無聊的蠢沿。但是,我們可以調(diào)味匾效!在這些例程的內(nèi)部搏予,我們可以隨時調(diào)用 nextsame
,samewith
,callwith
或 callame
來調(diào)用具有相同或不同參數(shù)的另一個候選者雪侥。但首先碗殷,我們來弄清楚它們都做什么?
主題
我們要測試的前 5 個例程的命名遵循如下約定:
-
call____
— 調(diào)用鏈中的下一個匹配的候選者并回到此處 -
next____
- 跳轉(zhuǎn)到鏈中的下一個匹配的候選者并且不回到此處 -
____same
- 使用 同一個參數(shù)就像用于當(dāng)前候選者一樣 -
____with
- 使用提供的這些新參數(shù)進行操作 -
samewith
- 從頭開始一個同樣的調(diào)用, 遵循一個新的分派鏈, 使用這些新的參數(shù)并且回到此處
samesame
不是什么稀奇的東西, 這種情況最好由常規(guī)循環(huán)代替速缨。主要的外賣是“call”锌妻,就是說你調(diào)用候選者并回到原地, 使用它的返回值或做更多的事情; “next”意味著前往下一個候選者,并使用其返回值作為當(dāng)前候選者的返回值; 然而末尾的 same
和 with
僅僅控制你是否想要使用同樣的參數(shù)就像你在當(dāng)前候選者中使用的那樣, 或者提供一個新的集合旬牲。
讓我們來玩玩這些東西吧仿粹!
It's all called the same...
我們嘗試的第一個例程是 callsame
。它使用和當(dāng)前候選者同樣的參數(shù)調(diào)用下一個匹配的候選者并返回該候選者的返回值原茅。
讓我們來修改下我們的 Middle
候選者以調(diào)用 callsame
然后打印出它的返回值:
calss Wide {}
calss Middle is Wide {}
class Narrow is Middle {}
multi foo(Narrow $v) { say 'Narrow', $v, 'from Narrow' }
multi foo(Middle $v) {
say 'Middle', $v;
my $result = callsame;
say "We're back! The Return value is $result";
'from Middle'
}
multi foo(Wide $v) { say 'Wide ', $v; 'from Wide' }
foo Middle;
# OUTPUT:
# Middle (Middle)
# Wide (Middle)
# We're back! The return value is from Wide
現(xiàn)在可以看到我們的單個 foo
調(diào)用導(dǎo)致了兩次調(diào)用吭历。第一次到了 Middle
, 因為它是我們給 foo
調(diào)用的類型對象。第二次到了 Wide
, 因為它是下一個能接收 Middle
類型的候選者; 在輸出中我們看到 Wide
被調(diào)用時仍舊使用了我們原來的 Middle
類型對象擂橘。最后, 我們回到了我們的 Middle
候選者中, 并把 Wide
候選者的返回值設(shè)置給 $result
變量晌区。
目前為止非常清晰, 讓我們試試修改下參數(shù)!
Have you tried to call them with...
正如我們所知, __with
變體允許我們使用不同的參數(shù)。我會使用和之前的例子相同的代碼, 區(qū)別就是現(xiàn)在我會執(zhí)行 callwith
, 并使用 Narrow
類型對象作為新的參數(shù):
class Wide {}
class Middle is Wide {}
class Narrow is Middle {}
multi foo(Narrow $v) { say 'Narrow', $v; 'from Narrow' }
multi foo(Middle $v) {
say 'Middle', $v;
my $result = callwith Narrow;
say "We're back! The return value is $result";
'from Middle'
}
multi foo(Wide $v) { say 'Wide ', $v; 'from Wide' }
foo Middle;
# OUTPUT:
# Middle (Middle)
# Wide (Narrow)
# We're back! The return value is from Wide
輸出的第一部分很清楚: 我們?nèi)耘f使用 Middle
調(diào)用 foo
, 并且先擊中 Middle
候選者通贞。然而, 下一行輸出有點古怪朗若。我們在 callwith
中使用了 Narrow
參數(shù), 所以究竟 Wide
候選者是怎么得到調(diào)用的而不是調(diào)用 Narrow
候選者呢?
原因是 call____
和 next____
例程使用與原始調(diào)用相同的調(diào)度鏈。由于 Narrow
候選者比 Middle
候選者更窄, 所以被拒絕, 在當(dāng)前的鏈條中不會被考慮昌罩。下一個 callwith
將會調(diào)用的候選者將是下一個匹配 Middle
的候選者 -- 而不是筆誤:Middle
是我們用于發(fā)起調(diào)度的參數(shù), 因此下一個候選者將是仍然可以接受該原始調(diào)用的參數(shù)的候選者哭懈。一旦發(fā)現(xiàn)下一個候選者,傳遞給 callwith
的新參數(shù)就會綁定到它身上, 這是你的工作, 以確保它們可以茎用。
讓我們在實戰(zhàn)中看一個更復(fù)雜的例子遣总。
Kicking It Up a Notch
我們會使用更多的 multies 和類型來擴展我們原來的例子:
class Wide {}
class Middle is Wide {}
class Narrow is Middle {}
subset Prime where .?is-prime;
subset NotPrime where not .?is-prime;
multi foo(Narrow $v) { say 'Narrow ', $v; 'from Narrow' }
multi foo(Middle $v) { say 'Middle ', $v; 'from Middle' }
multi foo(Wide $v) { say 'Wide ', $v; 'from Wide' }
multi foo(Prime $v) { say 'Prime ', $v; 'from Prime' }
multi foo(NotPrime $v) { say 'Non-Prime', $v; 'from Prime' }
foo Narrow; # OUTPUT: Narrow (Narrow)
foo Middle; # OUTPUT: Middle (Middle)
foo Wide; # OUTPUT: Wide (Wide)
foo 42; # OUTPUT: Non-Prime 42
foo 31337; # OUTPUT: Prime 31337
我們原來的三個類都是 Any 類型并且我們還創(chuàng)建了兩個 Any 的子集(subset): Prime
和 NotPrime
。其中 Prime
在類型上匹配素數(shù)而 NotPrime
在類型上匹配不是素數(shù)的數(shù)字或者匹配不含 .is-prime
方法的數(shù)字轨功。因為我們的三個自定義類沒有 .is-prime
方法, 所以它們都在類型上匹配 NotPrime
旭斥。
如果我們使用這種新格局重建之前的例子, 我們會得到和之前同樣的結(jié)果:
class Wide { }
class Middle is Wide { }
class Narrow is Middle { }
subset Prime where .?is-prime;
subset NotPrime where not .?is-prime;
multi foo (Narrow $v) { say 'Narrow ', $v; 'from Narrow' }
multi foo (Middle $v) {
say 'Middle ', $v;
my $result = callwith Narrow;
say "We're back! The return value is $result";
'from Middle'
}
multi foo (Wide $v) { say 'Wide ', $v; 'from Wide' }
multi foo (Prime $v) { say 'Prime ', $v; 'from Prime' }
multi foo (NotPrime $v) { say 'Non-Prime ', $v; 'from NotPrime' }
foo Middle;
# OUTPUT:
# Middle (Middle)
# Wide (Narrow)
# We're back! The return value is from Wide
原來的調(diào)用來到 Middle
候選者,它用 Narrow
類型對象 callwith
到 Wide
候選者中夯辖。
現(xiàn)在琉预,我們來混合一下, 用 42
而不是 Narrow
來調(diào)用。我們確實有一個 NotPrime
候選者蒿褂。 42
和原來的 Middle
都可以適應(yīng)這個候選者圆米。而且比原來的 Middle
候選者更寬, 并且它還處在調(diào)度鏈的上游。這可能會出錯!
class Wide { }
class Middle is Wide { }
class Narrow is Middle { }
subset Prime where .?is-prime;
subset NotPrime where not .?is-prime;
multi foo (Narrow $v) { say 'Narrow ', $v; 'from Narrow' }
multi foo (Middle $v) {
say 'Middle ', $v;
my $result = callwith 42;
say "We're back! The return value is $result";
'from Middle'
}
multi foo (Wide $v) { say 'Wide ', $v; 'from Wide' }
multi foo (Prime $v) { say 'Prime ', $v; 'from Prime' }
multi foo (NotPrime $v) { say 'Non-Prime ', $v; 'from NotPrime' }
foo Middle;
# OUTPUT:
# Middle (Middle)
# Type check failed in binding to $v; expected Wide but got Int (42)
# in sub foo at z2.p6 line 15
# in sub foo at z2.p6 line 11
# in block <unit> at z2.p6 line 19
哦啄栓,對娄帖,那個!我們給 callwith
的新參數(shù)不會影響調(diào)度, 所以盡管有一個候選者可以在調(diào)用鏈中進一步地處理我們的新參數(shù), 但不是下一個候選者可以處理原始的 args, 這是 callwith
所調(diào)用的昙楚。結(jié)果是拋出異常, 由于我們的新參數(shù)綁定給下一個調(diào)用者時失敗了近速。
誰是下一個?
這個能讓我們在調(diào)度鏈上抓住下一個匹配的候選者的趁手的小子例程是 nextcallee
。它不僅返回該候選者的 Callable
,而且它將其從鏈上移出削葱,以便下一個 next____
和 call____
會進入下一個候選者奖亚,下一個 nextcallee
將會移出并返回 next-next 候選者。所以, 讓我們回到我們之前的例子, 作點弊析砸!
class Wide { }
class Middle is Wide { }
class Narrow is Middle { }
subset Prime where .?is-prime;
subset NotPrime where not .?is-prime;
multi foo (Narrow $v) { say 'Narrow ', $v; 'from Narrow' }
multi foo (Middle $v) {
say 'Middle ', $v;
nextcallee;
my $result = callwith 42;
say "We're back! The return value is $result";
'from Middle'
}
multi foo (Wide $v) { say 'Wide ', $v; 'from Wide' }
multi foo (Prime $v) { say 'Prime ', $v; 'from Prime' }
multi foo (NotPrime $v) { say 'Non-Prime ', $v; 'from NotPrime' }
foo Middle;
# OUTPUT:
# Middle (Middle)
# Non-Prime 42
# We're back! The return value is from NotPrime
啊哈昔字!有用!代碼幾乎完全一樣首繁。唯一的變化是我們在調(diào)用 callwith
之前彈出了 nextcallee
調(diào)用作郭。它移除了無法處理新的 42
參數(shù)的 Wide
候選者,所以弦疮,從輸出可以看出夹攒,我們的調(diào)用進入了 NotPrime
候選者。
nexcallee
是精湛的胁塞,所以循環(huán)是一個挑戰(zhàn)咏尝,因為它會使用循環(huán)或thunk的調(diào)度程序來查找被調(diào)用者。所以它最常見和最簡單的用法是獲得...下一個被調(diào)用者闲先。如果你需要傳遞下一個被調(diào)用者, 你得先這樣做:
multi pick-winner (Int \s) {
my &nextone = nextcallee;
Promise.in(π2).then: { nextone s }
}
multi pick-winner { say "Woot! $^w won" }
with pick-winner ^5 .pick -> \result {
say "And the winner is...";
await result;
}
# OUTPUT:
# And the winner is...
# Woot! 3 won
Int
候選者接收 nextcallee
然后啟動一個會被并行執(zhí)行的 Promise, 在超時后返回状土。我們不能在這兒使用 nextsame
, 因為它會嘗試 nextsame
Promise 的代碼塊而非我們原來的子例程, 因此, nextcallee
節(jié)省了我們一整天時間无蜂。
我想我們已經(jīng)到達了令人眼花繚亂的例子的巔峰伺糠,我可以聽到觀眾的呼喊。 “這種東西有什么用呢斥季,反正训桶?只是制造了更多的 subs, 而不是混亂的 multis!“ 所以,讓我們來看看更多的真實世界的例子酣倾,以及接下來遇見的 nextsame
和 nextwith
舵揭!
Next one in line, please
我們來寫一個類!一個能做事的類躁锡!
role Things {
multi method do-it($place) {
say "I am {<eating sleeping coding weeping>.pick} at $place"
}
}
class Doer does Things {}
Doer.do-it: 'home' # 輸出: I am coding at home
我們碰不到role午绳,因為它是別人為我們而做的,保持它們本來的面目吧映之。但是拦焚,我們希望我們的 class 能做更多的事情!對于某些$place
杠输,我們希望它告訴我們一些更具體的東西赎败。另外,如果這個地方是 my new place
蠢甲,我們想知道哪些地方是新的僵刮。以下是代碼:
role Things {
multi method do-it($place) {
say "I am {<eating sleeping coding weeping>.pick} at $place"
}
}
class Doer does Things {
multi method do-it($place where .contains: 'home') {
nextsame if $place.contains: 'large';
nextwith "home with $<color> roof"
if $place ~~ /$<color>=[red|green|blue]/;
samewith method 'my new place';
}
multi method do-it('my new place') {
nextwith 'red home'
}
}
Doer.do-it: 'the bus'; # OUTPUT: I am eating at the bus
Doer.do-it: 'home'; # OUTPUT: I am sleeping at red home
Doer.do-it: 'large home'; # OUTPUT: I am sleeping at large home
Doer.do-it: 'red home'; # OUTPUT: I am eating at home with red roof
Doer.do-it: 'some new home'; # OUTPUT: I am eating at red home
Doer.do-it: 'my new place'; # OUTPUT: I am coding at red home
多了一點額外的代碼,并且沒有對提供該方法的 role 進行單個更改,我們添加了一大堆新功能搞糕。我們來看看我們使用的三個新的調(diào)度更改例程勇吊。
nextsame
和 nextwith
函數(shù)與它們的 callsame
和 callwith
對應(yīng)函數(shù)非常相似,除了它們不回到被調(diào)用的位置窍仰,它們的返回值將被用作當(dāng)前例程的返回值萧福。所以使用 nextsame
就像使用 return callsame
,但是使用較少的鍵入辈赋,并且編譯器能夠進行更多的優(yōu)化鲫忍。
我們添加到類中的第一個 multi 方法被調(diào)度到 $place
.contains 單詞 home
的位置。在方法的主體中钥屈,如果 $place
也包含單詞 large
悟民,我們使用 nextsame
,即使用與當(dāng)前方法相同的參數(shù)調(diào)用下一個匹配的候選者篷就。這是這里的關(guān)鍵射亏。我們不能全部再次調(diào)用我們的方法,因為它將進入無限循環(huán)重新分派給自己竭业。然而智润,由于 nextsame
在同一個調(diào)度鏈中使用下一個候選者,所以沒有循環(huán)發(fā)生未辆,我們正好得到 role Things
中的候選人者窟绷。
往下讀代碼中,nextwith
也隨之而來咐柜。當(dāng) $place
提到三種顏色之一時兼蜈,我們使用它。類似于 nextsame
拙友,它去下一個候選者为狸,除了我們給它一個新的參數(shù)使用之外。
最后遗契,我們來到 samewith
辐棒。與以前使用的例程不同,這個家伙從頭開始重新啟動調(diào)度牍蜂,所以它基本上就像再次調(diào)用該方法一樣漾根,除了你不必知道或使用它的實際名稱。我們用一組新的參數(shù)來調(diào)用 samewith
捷兰,從輸出中我們可以看到新的調(diào)度路徑通過我們添加到我們的類中的第二個 multi 路徑立叛,而不是繼續(xù)從 role 的 multi,就像我們的 next____
版本所做的那樣贡茅。
Last Call秘蛇!
包中的最后一個方法是 lastcall
其做。調(diào)用它截斷當(dāng)前的調(diào)度鏈,以便 next____
和 call____
例程不會有其它任何地方可去赁还。以下是一個例子:
multi foo (Int $_) {
say "Int: $_";
lastcall when *.is-prime;
nextsame when * %% 2;
samewith 6 when * !%% 2;
}
multi foo (Any $x) { say "Any $x" }
foo 6; say '----';
foo 2; say '----';
foo 1;
# OUTPUT:
# Int: 6
# Any 6
# ----
# Int: 2
# ----
# Int: 1
# Int: 6
# Any 6
我們所有的對 foo
的調(diào)用都會首先進入 Int
候選者妖泄。當(dāng)數(shù)字為 .is-prime,我們調(diào)用 lastcall
; 當(dāng)它是一個偶數(shù)艘策,我們調(diào)用 nextsame
; 而當(dāng)這是一個奇數(shù)時蹈胡,我們使用 6
作為參數(shù)來調(diào)用samewith
。
我們調(diào)用 foo
的第一個數(shù)字是6朋蔫,這不是素數(shù)罚渐,所以 lastcall
從不被調(diào)用。這是一個偶數(shù)驯妄,所以我們調(diào)用 nextsame
荷并,從輸出我們看到,我們已經(jīng)達到Any候選者青扔。
接下來源织,當(dāng)我們使用 2
調(diào)用 foo
時,這是一個素數(shù)和偶數(shù)微猖,我們調(diào)用 lastcall
和 nextcall
谈息。然而,因為 lastcall
被調(diào)用并截斷了調(diào)度鏈凛剥,所以 nextcall
從不會看到 Any 候選者侠仇,所以我們只有在輸出中調(diào)用 Int
候選者。
在最后一個例子中当悔,我們再次使用一個素數(shù)傅瞻,所以 lastcall
再一次被調(diào)用踢代。然而盲憎,數(shù)字是一個奇數(shù),所以我們使用 samewith
而不是 nextwith
胳挎。由于 samewith
從頭開始重新調(diào)度饼疙,它不在乎我們用 lastcall
截斷了以前的鏈。因此慕爬,輸出顯示我們經(jīng)過 Int
候選者兩次窑眯,第二次調(diào)用使用 nextsame
到達 Any 候選者,因為 samewith
上使用的數(shù)字不是素數(shù)医窿,而是偶數(shù)磅甩。
Wrapping It Up
為了整理這篇文章,我們將考察另一個領(lǐng)域姥卢,我們學(xué)到的例程可以派上用場:包裝東西卷要!以下是代碼:
use soft;
sub meower (\ッ, |c) {
nextwith "?? says {ッ}", |c when ッ.gist.contains: 'meow';
nextsame
}
&say.wrap: &meower;
say 'chirp';
say 'moo';
say 'meows!';
# OUTPUT:
# chirp
# moo
# ?? says meows!
我們使用 soft
pragma 來禁止內(nèi)聯(lián)渣聚,所以我們的包裝是理智的。我們有一個 meower
sub 來修改第一個參數(shù)僧叉,如果它 .contains 單詞 meow
奕枝,傳遞其余的參數(shù),如果有的話瓶堕,通過一個 Capture(就是 |c
這個東西)未修改隘道。所有剩下的調(diào)用都是使用 nextsame
原樣傳遞的,我們 .wrap meower
到 say
程序上郎笆,從輸出可以看出谭梗,一切都按照我們的意愿工作。
這是代碼的主要功能:meower
不知道什么 sub 被包裝了宛蚓! 然而默辨,它仍然可以在不發(fā)生問題的情況下設(shè)法調(diào)用它。
在這里苍息,我們把它放在 put 例程上缩幸,而且它沒有任何修改就可以正常工作:
use soft;
sub meower (\ッ, |c) {
nextwith "?? says {ッ}", |c when ッ.gist.contains: 'meow';
nextsame
}
&put.wrap: &meower;
put 'chirp';
put 'moo';
put 'meows!';
# OUTPUT:
# chirp
# moo
# ?? says meows!
Conclusion
今天,我們學(xué)到了一些強大的例程竞思,讓您可以從其他候選者中重用現(xiàn)有的多個候選者表谊。 callsame
和 callwith
允許您調(diào)用當(dāng)前調(diào)度鏈中的下一個匹配候選者,使用相同的參數(shù)或新集合盖喷。nextsame
和 nextwith
完成同樣的事情爆办,而不返回到調(diào)用現(xiàn)場。
samewith
子讓您從頭開始重新啟動調(diào)度鏈课梳,而無需知道當(dāng)前例程的名稱距辆。而 lastcall
和 nextcallee
可以通過截斷當(dāng)前的調(diào)度鏈,或者移出和操作下一個被調(diào)用者來操作當(dāng)前的調(diào)度鏈暮刃。
好好使用它們吧跨算!
-Ofun
原文: