7

在 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 所建议的,我将我的结果发布如下,签名版本也很有趣。

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
4

4 回答 4

10

TL; DR我写了另一个专注于使用when. 该答案的重点是使用将Signatures(Raku 强大的模式匹配构造)与where子句组合在一起的替代方法。

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

根据我对 Scala 的了解,一些/大多数 Scala 模式匹配实际上对应于使用 Raku 签名。(在这种情况下,保护子句通常是where子句。)

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

而不是仅仅匹配数字,这是 switch 语句所做的,你匹配本质上是对象的创建形式

Raku 签名涵盖了几个用例(是的,双关语)。其中包括 Raku 等效的函数式编程范式使用,其中一个匹配值或函数的类型签名(cf Haskell)和面向对象的编程范式使用,其中一个匹配嵌套数据/对象并提取所需的位(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。(这可以很好地捆绑成一个subsettype。)

我们可以定义multi对应于不同情况的其他 s,如果他们妈妈的名字与特定的正则表达式匹配或其他什么,可能会提取人腿的“名字”(“左”、“中”等)——你希望得到图片。

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

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

(在上面,我们在签名中添加了一个参数前缀 with|将所有剩余的结构/参数传递给一个 multi。鉴于我们对那些 slurped 的结构/数据什么都不做,我们可以只写(|)。)

不幸的是,我认为官方文档中没有提到签名解构。有人可以写一本关于 Raku 签名的书。(从字面上看。这当然是一种很好的方式——甚至是唯一的方式——写东西。我最喜欢的文章揭示了 Raku 签名的一些力量,是Moritz 从 2013 年开始的Pattern Matching and Unpacking 。谁创作了 Raku书。这是希望。)

Scalamatch/case和 Rakugiven/when似乎更简单

的确。

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

在缓解方面:

  • 更简单的情况可以使用given/when,就像您在问题正文中所写的那样;

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

于 2018-04-13T20:40:06.657 回答
7

从我在这个答案中看到的,这并不是真正意义上的保护模式的实现,就像Haskell 拥有它们一样。然而,Perl 6 确实有与 Scala 相同的保护:使用默认模式与 ifs 结合。Haskell 到 Perl 6 指南确实有一个关于警卫的部分。它暗示where用作警卫;所以这可能会回答你的问题。

于 2018-04-13T12:41:20.733 回答
6

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 的陷阱页面上提到这一点。

我们还应该考虑提交一个问题,以使 Rakudo 警告或编译失败,如果一个when构造以一个类型对象的编译时常量值开头,并以and(or &&, andthen, 等) 继续,which . 它可能在编译时失败或显示警告。


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

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

这利用when了括号内的语句修饰符(又名后缀)形式。在之前Int检查。_* > 10

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

我认为我的变体比我在此答案的先前版本中提出的其他选项更好,但仍然不能令人满意,因为我不喜欢Int出现这种情况。


最终,特别是如果/当 RakuAST 登陆时,我认为我们将尝试新的模式匹配形式。希望我们能想出一些好的东西来很好地消除这种疣。

真的吗?这是怎么回事?

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

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

显示:

TrueB
TrueA
(Int)
(Int)

构造and布尔值评估其左手参数。如果计算结果为False,则返回它,否则返回其右手参数。

在第一行中,'TrueA'boolean 计算结果为,True因此第一行返回右手参数'TrueB'

在第二行'TrueB'中,计算结果为True返回and其右手参数,在本例中为'TrueA'

但是第三行会发生什么?嗯,Int是一个类型对象。类型对象 boolean 评估为False! 所以and适当地返回它的左手参数是Int.say然后显示为(Int))。

这是问题的根源。

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

如果您使用这样的表达式作为if语句的条件,那么Int将布尔值评估为False并且您会得到一个假阴性。在这里,您使用的 awhen使用.ACCEPTS导致误报(它是一个整数,但它是任何整数,忽略假定的保护子句)。这个问题很可能属于陷阱页面

于 2018-04-14T20:40:34.827 回答
4

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

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在 case 中对多个语句进行分组Int,从而不必进行重复的类型检查。这也意味着如果我们不测试一个值,这些内部when检查根本不会发生。Int

    when Int:D {
        when $_ < 10 { say 5 }
        when 10      { say 10}
        when $_ > 10 { say 65}
    }
于 2021-03-09T04:36:46.617 回答