关于Raku函数签名中双分号的用法及相关疑问
先看官方文档里的这段示例代码:
enum DebugType <LOG WARNING ERROR>; #|[ Prints a message to stderr with a color-coded key. ] proto debug(DebugType:D $type, Str:D $message --> Bool:_) { note sprintf qb/\e[1;%dm[%s]\e[0m %s/, {*}, $type.key, $message } multi debug(LOG;; Str:D --> 32) { } multi debug(WARNING;; Str:D --> 33) { } multi debug(ERROR;; Str:D --> 31) { }
文档中“Long names”小节给出了双分号的说明:
To exclude certain parameters from being considered in multiple dispatch, separate them with a double semicolon.
附带的示例代码:
multi sub f(Int $i, Str $s;; :$b) { say "$i, $s, {$b.raku}" }; f(10, 'answer'); # OUTPUT: «10, answer, Any»
针对以上内容,有以下三个疑问及对应的解析:
疑问1:multi debug里为啥要把第一个参数排除在多派发考量外?排除后怎么确定派发哪个子例程?
首先要纠正一个误解:被排除的不是第一个参数,而是双分号后面的Str:D参数。第一个参数LOG/WARNING/ERROR是枚举字面量,本身就是多派发的核心匹配依据。这段代码的逻辑是:proto定义的签名要求第一个参数是DebugType类型实例,第二个是字符串。每个multi子例程已经用具体的枚举值作为第一个参数的匹配条件——这已经足够让Raku的多派发机制确定调用哪个multi。
把
Str:D放在双分号后面,是明确告诉Raku:这个参数不参与多派发的匹配判断。因为三个multi子例程的第二个参数都是Str:D,本来就不会影响派发选择,排除它只是避免编译器做不必要的检查,同时让代码意图更清晰。Raku确定派发的逻辑还是看第一个参数的具体枚举值:调用
debug(LOG, "xxx")时匹配第一个multi;调用debug(ERROR, "yyy")时匹配第三个,和双分号无关——双分号只是把第二个参数从派发判断里移除了而已。
疑问2:把双分号换成单逗号,输出没差异?
因为所有multi子例程的第二个参数都是Str:D,不管用逗号还是双分号,Raku的多派发机制在匹配时,这个参数的类型完全一致,不会影响最终选择哪个multi。用逗号的话,第二个参数会被纳入派发判断,但由于三个子例程的该参数类型无差异,判断结果和排除它时完全一样,最终调用的都是对应枚举值的multi,输出自然没区别。
但如果后续修改某个multi的第二个参数类型(比如改成
Int:D),差异就会显现:用逗号的话,Raku会同时根据第一个枚举值和第二个参数类型来派发;用双分号的话,第二个参数类型不影响派发,只要第一个参数匹配就会调用,此时若传入字符串就会触发参数类型不匹配的错误。
- 疑问3:只有一个multi sub f时,双分号怎么影响派发?“Long names”副标题为啥突兀?
双分号的影响:
哪怕只有一个multi子例程,双分号的作用依然是排除参数参与多派发。如果后续新增另一个multi sub f,就能看出区别:
比如新增:multi sub f(Int $i, Str $s;; :$b, :$c) { say "$i, $s, {$b.raku}, {$c.raku}" }此时调用
f(10, 'answer', :b(True)),Raku不会因为:$b这个命名参数的存在而选择第二个multi——因为双分号把命名参数排除在了派发判断之外,两个multi的派发判断只看前面的Int $i, Str $s,会因重复匹配报错。
如果把双分号换成逗号,第二个multi的签名是Int $i, Str $s, :$b, :$c,调用f(10, 'answer', :b(True))时会匹配第一个multi;调用f(10, 'answer', :b(True), :c(False))时会匹配第二个。“Long names”副标题突兀的原因:
这明显是文档排版的错误,双分号的功能和“Long names”(长参数名)没有直接关联,大概率是文档编辑时的归类失误,把双分号的说明放错了小节。
内容的提问来源于stack exchange,提问作者AlvaPan

