Hi, I am really looking forward to this feature. Looks great!

However I'd like to make few suggestions which I believe should be part of the first pattern matching experimental release. I'll include use-cases and try to explain why it would be better to do so.

(1) Pattern matching as first-class citizen¶

Everything in Ruby is dynamically accessible (methods, classes, blocks, etc.) so it would be pity if patterns would be an exception from that. There should be an object which will represent the pattern and which can be lifted from the pattern literal.

It may seem that just wrapping the pattern in a lambda as follows is enough to get an object which represents the pattern.

-> value do case value in ( 1 .. 10 ) => i do_something_with i end end

In some cases it is sufficient however lets explore some interesting use cases which cannot be implemented without the first-class pattern-matching.

First use-case to consider is searching for a value in a data structure. Let's assume we have a data-structure (e.g. some in memory database) and we want to provide an API to search for an element with a pattern matching e.g. #search . The structure stores log messages as follows ["severity", "message"] . Then something as follows would be desirable.

def drain_erros ( data ) # pops all messages matching at least one pattern # and evalueates the appropriate branch with the destructured log message # for each matched message data . pop_all case in [ "fatal" , message ] deal_with_fatal message in [ "error" , message ] deal_with_error message end end

There are few things to consider. Compared to the already working implementation there is no message given to the case since that will be later provided in the pop_all method. Therefore the case in here has to evaluate to an object which encapsulates the pattern matching allowing to match candidates from the data-structure later in the pop_all implementation. Another important feature is that the object has to allow to match a candidate without immediately evaluating the appropriate branch. It has to give the pop_all method a chance to remove the element from the data-structure first before the arbitrary user code from the branch is evaluated. That is especially important if the data-structure is thread-safe and does locking, then it cannot hold the lock while it runs arbitrary user code. Firstly it limits the concurrency since no other operation can be executed on the data-structure and secondly it can lead to deadlocks since the common recommendation is to never call a user code while still holding an internal lock.

Probably the simplest implementation which would allow the use-case work is to make case in without a message given behave as a syntax sugar for following.

case in [ /A/ , b ] b . succ end # turns into -> value do case value in [ /A/ , b ] -> { b . succ } end end

Then the implementation of pop_all could then look as follows.

def pop_all ( pattern ) each do | candidate | # assuming each locks the structure to read the candidate # but releases the lock while executing the block which could # be arbitrary user code branch_continuation = pattern . call ( candidate ) if branch_continuation # candidate matched delete candidate # takes a lock internally to delete the element branck_continuation . call end end end

In this example it never leaks the inner lock.

Second use case which somewhat expands the first one is to be able to implement receive method of the concurrent abstraction called Actors. ( receive blocks until matching message is received.) Let's consider an actor which receives 2 Integers adds them together and then replies to an actor which asks for a result with [:sum, myself] message then it terminates.

Actor . act do # block until frist number is received first = receive case in Numeric => value value end # block until second number is received, then add them sum = first + receive case in Numeric => value value end # when a :sum command is received with the sender reference # send sum back receive case in [ :sum , sender ] sender . send sum end end

It would be great if we could use pattern matching for messages as it is used in Erlang and in Elixir.

The receive method as the pop_all method needs to first find the first matching message in the mailbox without running the user code immediately, then it needs to take the matching message from the Actor's mailbox (while locking the mailbox temporarily) before it can be passed to the arbitrary user code in the case branch (without the lock held).

If case in without message is first class it could be useful to also have shortcut to define simple mono patterns.

case in [ :sum , sender ] sender . send sum end # could be just in [ :sum , sender ] { sender . send sum }

case in [ "fatal" , _ ] -> message message end # could be just, default block being identity function in [ "fatal" , _ ]

Then the Actor example could be written only as follows:

Actor . act do # block until frist number is received first = receive in Numeric # block until second number is received, then add them sum = first + receive in Numeric # when a :sum command is received with the sender reference # send sum back receive in [ :sum , sender ] { sender . send sum } end

(2) Matching of non symbol key Hashes¶

This was already mentioned as one of the problems to be looked at in future in the RubyKaigi's talk. If => is taken for as pattern then it cannot be used to match hashes with non-Symbol keys. I would suggest to use just = instead, so var = pat . Supporting non-Symbol hashes is important for use cases like:

Matching data loaded from JSON where keys are strings

case { "name" => "Gustav" , ** other_data } in "name" => ( name = /^Gu.*/ ), ** other name #=> "Gustav" other #=> other_data end

Using pattern to match the key

# let's assume v1 of a protocol sends massege {foo: data} # but v2 sends {FOO: data}, # where data stays the same in both versions, # then it is desirable to have one not 2 branches case message_as_hash in ( :foo | :FOO ) => data process data end

Could that work or is there a problem with parsing = in the pattern?

Note about in [:sum, sender] { sender.send sum } ¶

in [:sum, sender] { sender.send sum } is quite similar to -> syntax for lambdas. However in this suggestion above it would be de-sugared to -> value { case value; in [:sum, sender]; -> { sender.send sum }} which is not intuitive. A solution to consider would be to not to de-sugar the branch into another inner lambda but allow to check if an object matches the pattern (basically asking if the partial function represented by the block with a pattern match accepts the object). Then the example of implementing pop_all would look as follows.

def pop_all ( pattern ) each do | candidate | # assuming each locks the structure to read the candidate # but releases the lock while executing the block which could # be arbitrary user code # does not execute the branches only returns true/false if pattern . matches? ( candidate ) # candidate matched delete candidate # takes a lock internally to delete the element pattern . call candidate end end end

What are your thoughts?

Do you think this could become part of the first pattern matching release?