Search code examples
scalaswitch-statementrakuguard-clause

Does pattern match in Raku have guard clause?


In scala, pattern match has guard pattern:

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

Is the Raku version like this?

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

Is this right?

Update: as jjmerelo suggested, i post my result as follows, the signature version is also interesting.

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

Solution

  • TL;DR I've written another answer that focuses on using when. This answer focuses on using an alternative to that which combines Signatures, Raku's powerful pattern matching construct, with a where clause.

    "Does pattern match in Raku have guard clause?"

    Based on what little I know about Scala, some/most Scala pattern matching actually corresponds to using Raku signatures. (And guard clauses in that context are typically where clauses.)

    Quoting Martin Odersky, Scala's creator, from 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 cover several use cases (yay, puns). These include the Raku equivalent of the functional programming paradigmatic use in which one matches values' or functions' type signatures (cf Haskell) and the object oriented programming paradigmatic use in which one matches against nested data/objects and pulls out desired bits (cf Scala).

    Consider this Raku code:

    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"
    

    Notice where there's a close equivalent to a Scala pattern matching guard clause in the above -- where * > 40. (This can be nicely bundled up into a subset type.)

    We could define other multis that correspond to different cases, perhaps pulling out the "names" of the person's legs ('left', 'middle', etc.) if their mom's name matches a particular regex or whatever -- you hopefully get the picture.

    A default case (multi) that doesn't bother to deconstruct the person could be:

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

    (In the above we've prefixed a parameter in a signature with | to slurp up all remaining structure/arguments passed to a multi. Given that we do nothing with that slurped structure/data, we could have written just (|).)

    Unfortunately, I don't think signature deconstruction is mentioned in the official docs. Someone could write a book about Raku signatures. (Literally. Which of course is a great way -- the only way, even -- to write stuff. My favorite article that unpacks a bit of the power of Raku signatures is Pattern Matching and Unpacking from 2013 by Moritz. Who has authored Raku books. Here's hoping.)

    Scala's match/case and Raku's given/when seem simpler

    Indeed.

    As @jjmerelo points out in the comments, using signatures means there's a multi foo (...) { ...} for each and every case, which is much heavier syntactically than case ... => ....

    In mitigation:

    • Simpler cases can just use given/when, just like you wrote in the body of your question;

    • Raku will presumably one day get non-experimental macros that can be used to implement a construct that looks much closer to Scala's match/case construct, eliding the repeated multi foo (...)s.