Haskell-类似 Raku 中的模式匹配

Haskell-like pattern matching in Raku

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 只做两件事。

  1. 作为 proceedsucceed 的块来完成。
  2. $_ 设置为您给它的值。

这意味着 $_ 已经设置为您要进行智能匹配的值。

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 的类型,但您不是 [=25] =]解构 Hold。相反,您利用了 given 块在块内设置主题变量 ($_) 的事实。

其次(看起来你已经意识到了这一点,但我将其添加以供将来参考),重要的是要注意 given 而不是 保证详尽的模式匹配。您可以使用 default { die } 块(或者更语义化地使用 fatal stub operator: default {!!! 'unreachable'})来模拟该保证,但当然这是运行时检查而不是编译时检查.

我同意在 Raku 中使用优雅的语法会很好。但是 功能上 来说,我认为 Raku 比你想象的更接近你所描述的。

The closest thing to this in Raku seems to be multimethods.

Raku 确实支持多种方法。但是你展示的是多功能的。

我也认为多功能是 Raku 目前拥有的最接近您所描述的东西。但我也认为它们比你目前认为的更接近。


I want a "local" pattern matching expression

多功能局部的(词法作用域)。

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。 (并称为多功能多方法。)明确地说,您不需要使用对象来使用 Raku 中的签名进行模式匹配。