19

Haskell and Rust (and maybe other languages of which I am not aware) have a feature which they call "pattern matching". Here is an example in 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

The closest thing to this in Raku seems to be multi routines (both functions and methods).

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

But this doesn't help if I want a "local" pattern matching expression (like the case expression in the haskell snippet above). Something like this:

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

Which alas does not compile. So am I missing something or can Raku express this in some other way?

uzluisf
  • 2,586
  • 1
  • 9
  • 27
Dincio
  • 1,010
  • 1
  • 13
  • 25
  • Looking up 'functional-programming' and 'pattern-matching' suggest that it's just syntactic sugar, and specific Raku examples might be found at https://docs.raku.org/language/control#index-entry-case_statements_(given) , although I hope someone like @JonathanWorthington might correct me if that's not the case. See: https://stackoverflow.com/questions/2502354/what-is-pattern-matching-in-functional-languages?rq=1 – jubilatious1 Feb 18 '21 at 21:39
  • 3
    I think this is what @raiph is saying in his answer: pattern matching is essentially already present in Raku through multifunctions. At this point what I'm really asking for is pattern matching *in an anonymous expression*, which could just be implemented using synctatic sugar in conjuction with Raku's multifunctions or C#'s/C++'s/Java's/etc... visitor pattern (as is mentioned in https://stackoverflow.com/questions/2502354/what-is-pattern-matching-in-functional-languages?rq=1). Unfortunately, Raku lacks a stable macro system to implement this sugar yourself (also mentioned by @raiph). – Dincio Feb 19 '21 at 00:11
  • 1
    @jubilatious1 https://docs.raku.org/language/control#index-entry-case_statements_(given) only showcases the `given` expression, which doesn't really support full pattern matching. – Dincio Feb 19 '21 at 00:17
  • 2
    For anyone who is interested in this, It might be worthwhile to read https://wimvanderbauwhede.github.io/articles/roles-as-adts-in-raku/, which goes even further by using multifunction pattern matching along with true algebraic datatypes (as opposed to my bear classes). Also, it is another great example of why a Raku macro system is a really good idea haha – Dincio Feb 19 '21 at 00:24
  • @dincio "Unfortunately, Raku lacks a *stable macro* system to implement this sugar yourself" Fwiw one can implement any language mutation one wants right now using grarmmars/actions/slangs, ie non-macro functionality. And it's at least *roa* stable. That is to say, changes are subject to the stability constraint represented by the technical, cultural, and policy nexus that revolves around language versioning and *roast*. That said, RakuAST looks likely to clean things up. So I think deferring such language changes for a couple years is the right strategy for now for all but the most intrepid. – raiph Feb 19 '21 at 16:11
  • @rapih I stand corrected, thanks for the clarifications. – Dincio Feb 19 '21 at 23:10

3 Answers3

13

You are trying to use a Signature where no Signature is expected.

The Signature would more likely be part of the blocks, so it would look more like this:

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

That doesn't currently work as the blocks don't get any arguments.

It could be argued that this would be a useful feature to add.


Note that there is only two things that given does.

  1. Act like a block for proceed and succeed to finalize to.
  2. Set $_ to the value you are giving it.

So that means that $_ is already set to the value you are smartmatching against.

given Hold.new(:key<a>) {
    when Hold  { say .key }
    when Press { say .key }
    when Err   { say .msg }
    default { say "Unsupported" }
}
Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • 2
    Thanks for the comprehensive answer! I agree, I like to rely on pattern matching quite a lot when using languages that support it. Still the alternative approach you gave seems quite powerful already. – Dincio Feb 16 '21 at 23:42
  • 4
    Such a feature has been speculated in the past. Between the ongoing work on dispatch and the new compiler frontend coming up during the next year, it'll probably be rather easier to implement and to have it perform decently that it is today. – Jonathan Worthington Feb 17 '21 at 18:48
11

The syntax you tried was actually extremely close. Here's what you want:

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

A few things to note: as the change in syntax shows, you are matching on the type of the Hold, but you're not destructuring Hold. Instead, you're taking advantage of the fact that given blocks set the topic variable ($_) inside the block.

Second (and it looks like you already realized this, but I'm adding it for future reference), it's important to note that given blocks do not guarantee exhaustive pattern matching. You can simulate that guarantee with a default { die } block (or, perhaps more semantically, by using the fatal stub operator: default {!!! 'unreachable'}), but of course that's a runtime check rather than a compile-time one.

codesections
  • 8,900
  • 16
  • 50
  • 1
    Yes this is what I meant by "alternative approach" in the above comment, It's a pity not having access to a more advanced pattern matching feature. – Dincio Feb 16 '21 at 23:50
  • 3
    Your lack of whitespace alignment in the class definitions is killing me, ditto for the default block. ^_^ – user0721090601 Feb 17 '21 at 01:16
10

I agree it'll be nice to have elegant syntax for this in Raku. But functionally speaking I think Raku's a bit closer to what you describe than you think it is.

The closest thing to this in Raku seems to be multimethods.

Raku does support multimethods. But what you showed were multifunctions.

I too think multifunctions are the closest thing Raku currently has to what you describe. But I also think they're closer than you currently think they are.


I want a "local" pattern matching expression

Multifunctions are local (lexically scoped).

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

Yes, the above code is syntactically "off". Presuming RakuAST lands it'll presumably be particularly straight-forward to implement a nicer syntax. Perhaps:

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

where match implements the multi code I showed above, but:

  • Avoids need for an enclosing block;

  • Avoids need to explicitly write the function call;

  • Replaces multi match(...) boilerplate with relatively terse ->.

  • Makes else clause mandatory (to avoid grammar ambiguity but has the nice side effect of enforcing explicit handling of a failure to otherwise match).


I noticed that you introduced OO for your example. (And called multifunctions multimethods.) Just to be clear, you don't need to use objects to do pattern matching using signatures in Raku.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • Collectively "functions" and "methods" are called "routines" in Raku, right? That's what I gather from the docs. – uzluisf May 15 '23 at 13:14
  • 2
    @uzluisf that's pretty much right - formally JS & Python use ```function```, raku uses ```sub``` to mean the same thing - a piece of code that can be called with arguments. Then, in raku, both a Sub and a Method are specializations of a Routine which is a kind of Block. This is all described in the raku docs https://docs.raku.org/type/Routine and I also suggest you take a look at the type graph to get a better feel for how raku is itself build as a set of Objects https://docs.raku.org/type/Routine#typegraphrelations... – librasteve May 16 '23 at 18:25
  • 2
    Oh - and I think ```sub``` is a better choice really for this thing since it avoids confusion with functional programming terminology, it maintains continuity with perl and it's short to type ;-) – librasteve May 16 '23 at 18:27