问题描述
Haskell 和 Rust(以及我不知道的其他语言)有一个他们称之为“模式匹配”的发展。下面是 Haskell 中的一个例子:
data Event = HoldKey Char | PressKey Char | Err String
someFunc = let
someEvent <- doSomeStuff
-- What follows is a case expression using pattern matching
thingINeed <- case someEvent of
HoldKey keySym -> process keySym
PressKey keySym -> process keySym
Err err -> exit err
in keepDoingStuff
Raku 中与此最接近的似乎是 multimethods 多功能(在下面的答案中修复了术语,但 multimethods 也可以工作)。
class Hold { has $.key; }
class Press { has $.key; }
class Err { has $.msg; }
multi process(Hold (:$key)) { say $key; }
multi process(Press (:$key)) { say $key; }
multi process(Err (:$msg)) { say $msg; }
但是,如果我想要一个“本地”模式匹配表达式(如上面 haskell 片段中的 case
表达式),这无济于事。像这样:
given Hold.new(:key<a>) {
when Hold (:$key) { say $key }
when Press (:$key) { say $key }
when Err (:$msg) { say $msg }
default { say "Unsupported" }
}
哪一个不编译。那么我是不是遗漏了什么,或者 Raku 可以用其他方式表达吗?
解决方法
您正在尝试使用不需要签名的签名。
签名更有可能是块的一部分,所以它看起来更像这样:
given Hold.new(:key<a>) {
when Hold -> (:$key) { say $key }
when Press -> (:$key) { say $key }
when Err -> (:$msg) { say $msg }
default { say "Unsupported" }
}
这目前不起作用,因为块没有任何参数。
可以说这是添加的有用功能。
请注意,given
只有两件事。
- 充当
proceed
和succeed
完成的块。 - 将
$_
设置为您提供的值。
这意味着 $_
已经设置为您智能匹配的值。
given Hold.new(:key<a>) {
when Hold { say .key }
when Press { say .key }
when Err { say .msg }
default { say "Unsupported" }
}
,
您尝试的语法实际上非常接近。这就是你想要的:
class Hold { has $.key; }
class Press { has $.key; }
class Err { has $.msg; }
given Hold.new(:key<a>) {
when Hold { say .key }
when Press { say .key }
when Err { say .msg }
default { say "Unsupported" }
}
需要注意的几点:正如语法中的变化所示,您匹配 Hold
的类型,但您不是解构 Hold
。相反,您正在利用 when
块将块内的主题变量 ($_
) 设置为等于匹配这一事实。
第二(看起来您已经意识到这一点,但我正在添加它以供将来参考),重要的是要注意 given
块不保证详尽的模式匹配。您可以使用 default { die }
块模拟该保证,但这当然是运行时检查而不是编译时检查。
我同意在 Raku 中有优雅的语法会很好。但从功能来说,我认为 Raku 比您想象的更接近您所描述的内容。
在 Raku 中与此最接近的似乎是多方法。
Raku 确实支持多种方法。但是你展示的是多功能的。
我也认为多功能是 Raku 目前与您所描述的最接近的东西。但我也认为它们比你目前认为的更接近。
我想要一个“本地”模式匹配表达式
多功能是本地的(词法范围)。
class Hold { has $.key; }
class Press { has $.key; }
class Err { has $.msg; }
{
match Hold.new: :key<a> ;
multi match (Hold (:$key)) { say $key }
multi match (Press (:$key)) { say $key }
multi match (Err (:$msg)) { say $msg }
multi match ($) { say "unsupported" }
}
# match; <--- would be a compile-time fail if not commented out
是的,上面的代码在语法上是“关闭的”。假设 RakuAST 登陆它可能会特别直接地实现更好的语法。也许:
match Hold.new: :key<a>
-> Hold (:$key) { say $key }
-> Press (:$key) { say $key }
-> Err (:$msg) { say $msg }
else { say "unsupported" }
其中 match
实现了我上面展示的 multi
代码,但是:
-
避免需要封闭块;
-
避免需要显式编写函数调用;
-
用相对简洁的
multi match(...)
替换->
样板。 -
强制
else
子句(以避免语法歧义,但有一个很好的副作用,即强制显式处理以其他方式匹配的失败)。
我注意到您为示例引入了 OO。 (并称为多功能 multimethods。)需要明确的是,您不需要使用对象来使用 Raku 中的签名进行模式匹配。