Raku 中类似 Haskell 的模式匹配

问题描述

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 的类型,但您不是解构 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 中的签名进行模式匹配。