Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell-like pattern matching in Raku

Haskell and Rust (and mabye other languages of which I am not aware) have a fature 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 multimethods multifunctions (terminology fixed in an answer below, but multimethods would also work).

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?

like image 426
Dincio Avatar asked Feb 16 '21 23:02

Dincio


3 Answers

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" }
}
like image 100
Brad Gilbert Avatar answered Oct 22 '22 04:10

Brad Gilbert


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.

like image 29
raiph Avatar answered Oct 22 '22 04:10

raiph


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.

like image 11
codesections Avatar answered Oct 22 '22 04:10

codesections