Raku 中类似 Haskell 的模式匹配

Din*_*cio 20 haskell functional-programming pattern-matching 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
Run Code Online (Sandbox Code Playgroud)

在 Raku 中与此最接近的似乎是 多方法 multifunctions(术语在下面的答案中固定,但 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; }
Run Code Online (Sandbox Code Playgroud)

但是,如果我想要一个“本地”模式匹配表达式(如case上面 haskell 片段中的表达式),这无济于事。像这样的东西:

given Hold.new(:key<a>) {
    when Hold (:$key)  { say $key }
    when Press (:$key) { say $key }
    when Err (:$msg)   { say $msg }
    default            { say "Unsupported" }
}
Run Code Online (Sandbox Code Playgroud)

唉,不能编译。那么我是不是遗漏了什么,或者 Raku 可以用其他方式表达吗?

Bra*_*ert 13

您正在尝试使用不需要签名的签名。

签名更有可能是块的一部分,所以它看起来更像这样:

given Hold.new(:key<a>) {
    when Hold  -> (:$key) { say $key }
    when Press -> (:$key) { say $key }
    when Err   -> (:$msg) { say $msg }
    default { say "Unsupported" }
}
Run Code Online (Sandbox Code Playgroud)

这目前不起作用,因为块没有任何参数。

可以说这将是一个有用的添加功能。


请注意,只有两件事可以given做。

  1. 就像一个块来完成proceedsucceed最终确定。
  2. 设置$_为您提供的值。

所以这意味着它$_已经设置为您正在智能匹配的值。

given Hold.new(:key<a>) {
    when Hold  { say .key }
    when Press { say .key }
    when Err   { say .msg }
    default { say "Unsupported" }
}
Run Code Online (Sandbox Code Playgroud)

  • 过去曾推测过这样的特征。在正在进行的调度工作和明年即将推出的新编译器前端之间,它可能会更容易实现并使其像今天一样正常运行。 (3认同)
  • 感谢您的全面回答!我同意,在使用支持它的语言时,我非常喜欢依赖模式匹配。不过,您提供的替代方法似乎已经相当强大了。 (2认同)

cod*_*ons 10

您尝试的语法实际上非常接近。这是你想要的:

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" }
}
Run Code Online (Sandbox Code Playgroud)

需要注意的几件事:正如语法中的变化所示,您正在匹配的类型Hold,但您不是在解构 Hold。相反,您正在利用when块将块内的主题变量 ( $_)设置为等于匹配这一事实。

其次(看起来您已经意识到这一点,但我正在添加它以供将来参考),重要的是要注意given块并不能保证详尽的模式匹配。您可以使用default { die }块模拟该保证,但这当然是运行时检查而不是编译时检查。

  • 你在类定义中缺乏空格对齐简直要了我的命,默认块也是如此。^_^ (3认同)

rai*_*iph 10

我同意在 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
Run Code Online (Sandbox Code Playgroud)

是的,上面的代码在语法上是“关闭的”。假设 RakuAST 登陆它可能会特别直接地实现更好的语法。也许:

match Hold.new: :key<a>
  -> Hold  (:$key)  { say $key }
  -> Press (:$key)  { say $key }
  -> Err   (:$msg)  { say $msg }
  else              { say "unsupported" }

Run Code Online (Sandbox Code Playgroud)

wherematch实现了multi我上面显示的代码,但是:

  • 避免需要封闭块;

  • 避免需要显式编写函数调用;

  • 替代对象multi match(...)样板相对简洁->

  • else条款强制性的(为了避免歧义的语法,但有执行失败的显式处理以其他方式匹配的很好的副作用)。


我注意到你为你的例子引入了 OO。(并称为多功能 multimethods。) 需要明确的是,您不需要使用对象来使用 Raku 中的签名进行模式匹配。

  • @uzluisf 这非常正确 - JS 和 Python 正式使用 ```function```,raku 使用 ```sub``` 来表示同样的事情 - 一段可以用参数调用的代码。然后,在 raku 中,Sub 和 Method 都是 Routine 的特化,而 Routine 是一种 Block。这一切都在 raku 文档 https://docs.raku.org/type/Routine 中进行了描述,我还建议您查看类型图,以更好地了解 raku 本身是如何构建为一组对象的 https://docs.raku.org/type/Routine ://docs.raku.org/type/Routine#typegraphrelations... (3认同)
  • 哦 - 我认为 ```sub``` 对于这件事来说确实是一个更好的选择,因为它避免了与函数式编程术语的混淆,它保持了与 perl 的连续性,并且打字很短;-) (2认同)