Issue #14912 has been updated by pitr.ch (Petr Chalupa).


I've intentionally focused on use-cases because: I wanted to make clear why Ruby should to support this; and I've included a possible solution only to make it more clear, since I think Kazuki or somebody else who also spend significant time working on this is the best person to figure out how to support it.

Regarding yours comments. 

(1) What I am proposing is different in one key aspect, it allows the `pop_all` method to execute its own code after the pattern is matched but **before** the branch with the user code is executed. Therefore the pattern of only wrapping `case in` in lambda is not sufficient to implement this.

To clarify I have described two approaches: First `case in <pattern>; <expr>; ...; end` => `-> x { case x in <pattern>; -> { <expr> }; ...; end }`. Second is without the inner lambda `case in <pattern>; <expr>; ...; end` => `pm = -> x { case x in <pattern>;  <expr>; ...; end }` however an object in  `pm` has to have a `accepts?(value)` method for the `pop_all` method to be able to execute just the pattern part without the bodies. I think the second is better.

(2) Thanks for pointing out the ambiguity problem. Id like to explore ideas how to deal with the ambiguity in `{ x => 1 }`:

* Simply raising error when the hash cannot be matched exactly.  Could often fail too surprisingly.
* `x` would be an array of values. What it should be if the value is only one though? It would produce mixed results unless always checked with `in { x => 1 } if Array === x` 
* Evaluate the branch for each matched x. Combination explosion could be a problem for `[{a=>1},{b=>2},...]`, would it run the branch for each value combination assigned to `a` and `b` variables? 
* Force users to write `{ *x => 1 }` when there is possibility of multiple results. Then x would be Array of all the matched keys (could be just one). This is however not that simple: `{ (*k = /^pre/) => *v }` applied to `{'pre-a'=>1, 'pre-b'=>2}`  could be `k=%w[pre-a pre-b]` and `v=[a,2]`; and then even more complicated `{ 1..10 => *([_, second]) }` applied to `{1=>[1,:a],2=>[2,:b]}` would produce `second=[:a,:b]`. Feels like this should be possible to define precisely.

(3) Indeed the shortcut I've proposed would be problematic, could it rather be `-> in <pattern> {}` then?


----------------------------------------
Feature #14912: Introduce pattern matching syntax
https://bugs.ruby-lang.org/issues/14912#change-78567

* Author: ktsj (Kazuki Tsujimoto)
* Status: Assigned
* Priority: Normal
* Assignee: ktsj (Kazuki Tsujimoto)
* Target version: 2.7
----------------------------------------
I propose new pattern matching syntax.

# Pattern syntax
Here's a summary of pattern syntax.

```
# case version
case expr
in pat [if|unless cond]
  ...
in pat [if|unless cond]
  ...
else
  ...
end

pat: var                                                   # Variable pattern. It matches any value, and binds the variable name to that value.
   | literal                                               # Value pattern. The pattern matches an object such that pattern === object.
   | Constant                                              # Ditto.
   | var_                                                  # Ditto. It is equivalent to pin operator in Elixir.
   | (pat, ..., *var, pat, ..., id:, id: pat, ..., **var)  # Deconstructing pattern. See below for more details.
   | pat(pat, ...)                                         # Ditto. Syntactic sugar of (pat, pat, ...).
   | pat, ...                                              # Ditto. You can omit the parenthesis (top-level only). 
   | pat | pat | ...                                       # Alternative pattern. The pattern matches if any of pats match.
   | pat => var                                            # As pattern. Bind the variable to the value if pat match.

# one-liner version
$(pat, ...) = expr                                         # Deconstructing pattern.
```

The patterns are run in sequence until the first one that matches.
If no pattern matches and no else clause, NoMatchingPatternError exception is raised.

## Deconstructing pattern
This is similar to Extractor in Scala.

The patten matches if:

* An object have #deconstruct method
* Return value of #deconstruct method must be Array or Hash, and it matches sub patterns of this

```
class Array
  alias deconstruct itself
end

case [1, 2, 3, d: 4, e: 5, f: 6]
in a, *b, c, d:, e: Integer | Float => i, **f
  p a #=> 1
  p b #=> [2]
  p c #=> 3
  p d #=> 4
  p i #=> 5
  p f #=> {f: 6}
  e   #=> NameError
end
```

This pattern can be used as one-liner version like destructuring assignment.

```
class Hash
  alias deconstruct itself
end

$(x:, y: (_, z)) = {x: 0, y: [1, 2]}
p x #=> 0
p z #=> 2
```

# Sample code
```
class Struct
  def deconstruct; [self] + values; end
end

A = Struct.new(:a, :b)
case A[0, 1]
in (A, 1, 1)
  :not_match
in A(x, 1) # Syntactic sugar of above
  p x #=> 0
end
```

```
require 'json'

$(x:, y: (_, z)) = JSON.parse('{"x": 0, "y": [1, 2]}', symbolize_names: true)
p x #=> 0
p z #=> 2
```

# Implementation
* https://github.com/k-tsj/ruby/tree/pm2.7-prototype
   * Test code: https://github.com/k-tsj/ruby/blob/pm2.7-prototype/test_syntax.rb

# Design policy
* Keep compatibility
   * Don't define new reserved words
   * 0 conflict in parse.y. It passes test/test-all
* Be Ruby-ish
   * Powerful Array, Hash support
   * Encourage duck typing style
   * etc
* Optimize syntax for major use case
   * You can see several real use cases of pattern matching at following links :)
      * https://github.com/k-tsj/power_assert/blob/8e9e0399a032936e3e3f3c1f06e0d038565f8044/lib/power_assert.rb#L106
      * https://github.com/k-tsj/pattern-match/network/dependents




-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>