Raku中类似Haskell的模式匹配

19
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中最接近这个的东西似乎是多例程(既包括函数也包括方法)。
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 有其他的表达方式?


查找“函数式编程”和“模式匹配”表明它只是语法糖,具体的Raku示例可能可以在https://docs.raku.org/language/control#index-entry-case_statements_(given)找到,尽管我希望像@JonathanWorthington这样的人能够纠正我,如果不是这种情况的话。请参见:https://dev59.com/13E95IYBdhLWcg3wApDk?rq=1 - jubilatious1
3
我认为@raiph在他的回答中所说的是:通过多功能,模式匹配已经在Raku中存在。目前我真正想问的是在匿名表达式中的模式匹配,这可以使用语法糖与Raku的多功能或C# / C++ / Java等...访问者模式结合实现(如https://dev59.com/13E95IYBdhLWcg3wApDk?rq=1中所述)。不幸的是,Raku缺乏稳定的宏系统来自行实现此语法糖(@raiph也提到了这一点)。 - Dincio
1
@jubilatious1 https://docs.raku.org/language/control#index-entry-case_statements_(given) 只展示了 given 表达式,它并不真正支持完整的模式匹配。 - Dincio
2
对于任何对此感兴趣的人,阅读https://wimvanderbauwhede.github.io/articles/roles-as-adts-in-raku/可能是值得的。该文章进一步使用多功能模式匹配以及真正的代数数据类型(而不是我的熊类),这也是另一个很好的例子,说明为什么Raku宏系统是一个非常好的想法,哈哈。 - Dincio
@dincio "不幸的是,Raku缺乏一个稳定的宏系统来实现这种语法糖"。FWIW,现在可以使用语法/操作/俚语实现任何语言变异,即非宏功能。而且至少是roa稳定的。也就是说,变化受到围绕语言版本控制和roast的技术、文化和政策纽带所代表的稳定性约束。话虽如此,RakuAST看起来有可能清理事情。因此,我认为推迟这样的语言变化几年对于除了最勇敢的人之外的所有人来说都是正确的策略。 - raiph
@rapih 我改口了,感谢你的澄清。 - Dincio
3个回答

13

您试图在不期望签名的地方使用签名。

签名更可能是块的一部分,因此它应该像这样:

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" }
}

2
感谢您提供如此全面的答案!我同意,当使用支持模式匹配的语言时,我很喜欢依赖于模式匹配。不过,您提供的另一种方法已经非常强大了。 - Dincio
4
过去已经有人猜测过这种特性。在处理任务的持续工作和新的编译器前端即将到来的情况下,实现它并让其表现得体会比今天要容易得多。 - Jonathan Worthington

11

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

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 。 相反,您利用了 given 块在块内设置主题变量( $ _ )的事实。

第二点(看起来您已经意识到了,但我会加上以供将来参考),重要的是要注意, given 块不保证穷举模式匹配。 您可以使用 default {die} 块模拟该保证(或者可能更语义化地使用致命存根运算符默认值{!!!'不可达'}),但当然这是运行时检查而不是编译时检查。


1
是的,这就是我在上面评论中所说的“替代方法”,很遗憾没有更高级的模式匹配功能可用。 - Dincio
3
你在类定义中缺少空格对齐让我很难受,同样的情况也出现在默认块中。^_^ - user0721090601

10

我同意,在 Raku 中拥有优雅的语法会很好。但就功能而言,我认为 Raku 比你想象的更接近你所描述的。

Raku 中最接近此功能的是多方法(multimethods)。

Raku 确实支持多方法(multimethods)。但是你展示的是多函数(multifunctions)。

我也认为多函数是 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 能够实现,那么实现一种更好的语法应该就非常简单了。也许可以像下面这样:

是的,上面的代码在语法上有些错误。假设 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 分支是强制性的(避免语法歧义,但具有实施显式处理无法匹配的情况的好处)。


我注意到您在示例中引入了面向对象编程。 (并称多功能函数为多方法。)只是为了明确起见,在 Raku 中使用签名进行模式匹配时不需要使用对象。


在Raku中,“函数”和“方法”合称为“例程”,对吗?这是我从文档中了解到的。 - uzluisf
2
@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#typegraphrelations... - librasteve
2
哦 - 我认为 sub 对于这件事情来说确实是更好的选择,因为它避免了与函数式编程术语混淆,保持了与 Perl 的连续性,并且打起来很短 ;-) - librasteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接