Raku 中的模式匹配是否有保护子句?

Does pattern match in Raku have guard clause?

在 scala 中,pattern matchguard pattern:

val ch = 23
val sign = ch match { 
    case _: Int if 10 < ch  => 65 
    case '+' =>  1 
    case '-' =>  -1 
    case  _  =>  0 
}

Raku版是这样的吗?

my $ch = 23;
given $ch  {
    when Int and * > 10 { say 65}
    when '+' { say 1  }
    when '-' { say -1 }
    default  { say 0  }
}

这样对吗?

更新: 按照jjmerelo的建议,我post我的结果如下,签名版也有意思。

multi washing_machine(Int \x where * > 10 ) { 65 }
multi washing_machine(Str \x where '+'    ) { 1  }
multi washing_machine(Str \x where '-'    ) { -1 }
multi washing_machine(\x)                   { 0  }

say washing_machine(12);      # 65
say washing_machine(-12);     # 0
say washing_machine('+');     # 1
say washing_machine('-');     # -1
say washing_machine('12');    # 0
say washing_machine('洗衣机'); # 0

从我在this answer, that's not really an implementation of a guard pattern中所见,同理Haskell也有。然而,Perl 6 确实具有与 Scala 具有相同意义的守卫:使用默认模式与 ifs 相结合。 Haskell to Perl 6 guide does have a section on guards。它暗示使用 where 作为守卫;这样可能会回答你的问题。

TL;DR 我写了另一个着重于使用 when 的答案。这个答案的重点是使用一种替代方法,将 Raku 强大的模式匹配结构 Signatureswhere 子句结合起来。

"Raku 中的模式匹配是否有保护子句?"

基于我对Scala的了解,some/most Scala模式匹配实际上对应于使用Raku签名。 (在这种情况下,保护条款通常是 where clauses。)

引用 Scala 的创建者 Martin Odersky,来自 The Point of Pattern Matching in Scala

instead of just matching numbers, which is what switch statements do, you match what are essentially the creation forms of objects

Raku signatures 涵盖多个用例(是的,双关语)。这些包括函数式编程范式使用的 Raku 等价物,其中匹配值或函数的类型签名(cf Haskell)和面向对象编程范式使用,其中匹配嵌套 data/objects 并拉取输出所需的位(cf Scala)。

考虑这个 Raku 代码:

class body { has ( $.head, @.arms, @.legs ) } # Declare a class (object structure).

class person { has ( $.mom, $.body, $.age ) } # And another that includes first.

multi person's-age-and-legs                   # Declare a function that matches ...

  ( person                                    # ... a person ...

    ( :$age where * > 40,                     # ... whose age is over 40 ...

      :$body ( :@legs, *% ),                  # ... noting their body's legs ...

      *% ) )                                  # ... and ignoring other attributes.

  { say "$age {+@legs}" }                     # Display age and number of legs.

my $age = 42;                                 # Let's demo handy :$var syntax below.

person's-age-and-legs                         # Call function declared above ...

  person                                      # ... passing a person.

    .new:                                     # Explicitly construct ...

      :$age,                                  # ... a middle aged ...

      body => body.new:
        :head,
        :2arms,
        legs => <left middle right>           # ... three legged person.

# Displays "42 3"

注意上面有一个与 Scala 模式匹配保护子句非常接近的等效项 -- where * > 40。 (这可以很好地捆绑成 subset type。)

我们可以定义对应于不同情况的其他 multis,也许可以提取人腿的“名称”('left'、'middle' 等)妈妈的名字与特定的正则表达式或其他任何内容匹配 - 希望您能理解。

一个默认情况(multi)不费心去解构这个人可能是:

multi person's-age-and-legs (|otherwise)
  { say "let's not deconstruct this person" }

(在上文中,我们在签名中为参数添加了 |slurp up all remaining structure/arguments passed to a multi 的前缀。假设我们不对 structure/data 执行任何操作,我们可以只写(|).)

不幸的是,我认为官方文档中没有提到签名解构。有人可以写一本关于 Raku 签名的书。 (从字面上看。这当然是一种很好的方式 - 甚至是唯一的方式 - 来写东西。我最喜欢的文章是 Pattern Matching and Unpacking 从 2013 年起,作者 Moritz。谁 人撰写了 Raku 书籍。希望如此。)

Scala 的 match/case 和 Raku 的 given/when 似乎更简单

确实如此。

正如@jjmerelo 在评论中指出的那样,使用签名意味着每个案例都有一个 multi foo (...) { ...},这在语法上比 case ... => ....

重得多

缓解中:

  • 比较简单的情况可以用given/when,就像你在问题正文中写的那样;

  • Raku 大概有一天会得到非实验性的宏,可用于实现一个看起来更接近 Scala 的 match/case 构造的构造,省略重复的multi foo (...)s.

TL;DR 您是否遇到过我所说的 WTF?!?:when Type and ... 无法检查 and 子句。这个答案讨论了 when 有什么问题以及如何解决它。我写了另一个答案,重点是使用 where 和签名。

如果你想坚持使用 when,我建议这样做:

when (condition when Type) { ... } # General form
when (* > 10 when Int) { ... }     # For your specific example

这(imo)并不令人满意,但它确实首先检查 Type 作为守卫,然后检查守卫是否通过的条件,并按预期工作。


“这样对吗?”

没有

given $ch {
  when Int and * > 10 { say 65}
}

此代码表示 65 对于 任何 给定整数,而不仅仅是 10!

卧槽?!? Imo 我们应该在 Raku's trap page.

上提到这个

如果 when 构造以类型对象的编译时常量值开始,并以 and 继续(或 &&andthen 等),其中 .它可能会在编译时失败或显示警告。


这是我能想到的最佳选择:

when (* > 10 when Int) { say 65 }

这利用了括号内 when 的语句修饰符(又名后缀)形式。 Int* > 10.

之前 检查

这受到 Brad++ 的新答案的启发,如果您针对单个保护条款编写多个条件,该答案看起来不错。

我认为我的变体比我在此答案的先前版本中提出的其他选项更好,但仍然不能令人满意,因为我不喜欢即将到来的 Int 条件之后。


最终,尤其是 if/when RakuAST 登陆,我认为我们将尝试新的模式匹配形式。希望我们能想出一些好的方法来很好地消除这种疣。

真的吗?怎么回事?

我们可以开始看到这段代码的潜在问题:

.say for ('TrueA' and 'TrueB'),
         ('TrueB' and 'TrueA'),
         (Int and 42),
         (42 and Int)

显示:

TrueB
TrueA
(Int)
(Int)

and 构造布尔值计算其左侧参数。如果计算结果为 False,则它 returns,否则它 returns 它的右手参数。

在第一行中,'TrueA' 布尔值的计算结果为 True,因此第一行 returns 右侧参数 'TrueB'.

在第二行中,'TrueB' 的计算结果为 True,因此 and returns 它的右手参数,在本例中为 'TrueA'.

但是第三行发生了什么?好吧,Int 是一个 type object。类型对象布尔值评估为 False!因此 and 适当地 returns 它的左手参数是 Int.say 然后显示为 (Int))。

这就是问题的根源。

(为了继续苦涩的结局,编译器计算表达式 Int and * > 10;立即 returns and 的左侧参数,即 Int;然后成功地将 Int 与任何整数 given 匹配——完全忽略看起来像保护子句的代码(and ... 位)。)

如果您使用这样的表达式作为 if 语句的条件,则 Int 的布尔值计算结果为 False 并且您会得到假阴性.在这里,您使用的是 when,它使用 .ACCEPTS which leads to a false positive (it is an integer but it's any integer, disregarding the supposed guard clause). This problem quite plausibly belongs on the traps page.

几年前我写了一条评论提到你必须更明确地匹配 $_ 像这样:

my $ch = 23;
given $ch  {

    when $_ ~~ Int and $_ > 10 { say 65}

    when '+' { say 1  }
    when '-' { say -1 }
    default  { say 0  }
}

回到这个问题后,我意识到还有另一种方法。
when 可以安全地位于另一个 when 结构中。

my $ch = 23;
given $ch  {

    when Int:D {
        when $_ > 10 { say 65}
        proceed
    }

    when '+' { say 1  }
    when '-' { say -1 }
    default  { say 0  }
}

请注意,内部 whensucceed 超出外部,后者将 succeed 超出 given 块。
如果内部 when 不匹配,我们要继续进行外部 when 检查和 default,所以我们调用 proceed.

这意味着我们还可以将多个 when 语句分组到 Int 案例中,从而避免重复进行类型检查。这也意味着如果我们不测试 Int 值,那些内部 when 检查根本不会发生。

    when Int:D {
        when $_ < 10 { say 5 }
        when 10      { say 10}
        when $_ > 10 { say 65}
    }