Issue #14912 has been updated by akr (Akira Tanaka).


ktsj (Kazuki Tsujimoto) wrote:
> Thanks for the feedback.
> 
> > But I feel the deconstruct method of Struct in the sample code is tricky
> > because it duplicates values.
> 
> > - Deconstruction seems fragile;  For instance the following case statement matches, which is very counter-intuitive.
> 
> It is trade-off with duck typing.
> 
> Consider following case.
> 
> ```ruby
> class MyA
>   def deconstruct
>     dummy = A[nil, nil]
>     return dummy, my_x, my_y
>   end
> end
> 
> obj = MyA.new
> 
> case obj
> in A(x, y)
>   ...
> end
> ```
> 
> We can match the pattern even if `obj` is not an instance of `A` class.

Hm.  I didn't explan my intent well.

My intent is deconstructing pattern is not well correspondence to
pattern matching of functional languages.

In functional languages, a data type is defined with
constructors and their arguments.

For example list of int can be defined in OCaml as follows.

```
type intlist = 
  | Inil
  | Icons of int * intlist
```

There are two constructors:
- constructor for empty list, Inil. It has no arguments.
- constructor for non-empty list, Icons. It has two arguments: first element and remaining list.

We can use pattern matching on the value of a data type.

For example, the length of list of int can be defined as follows.

```
let rec len il =
  match il with
  | Inil -> 0
  | Icons (_, il1) -> 1 + len il1
```

pattern matching distinguish the constructor of a value and
extract arguments of their constructors.

I think Ruby's pattern matching should support this style.

In Ruby, a data type of functional language can be implemented as
multiple classes: one class for one constructor.

```
class Inil
  def initialize()
  end
end
class Icons
  def initialize(e, il)
    @e = e
    @il = il
  end
end
```

(More realistic example, such as AST, may be more interesting.)

So, pattern matching need to distinguish class (correspond to constructor)
AND extract arguments for constructor.

In your proposal, it needs that deconstruct method must be implemented like
your Struct#deconstruct.

This means that, if deconstruct method is not defined like Struct#deconstruct,
Ruby's pattern matching is not usable as pattern matching of functional
languages.

For example, your Array#deconstruct and Hash#deconstruct is not like
Struct#deconstruct.
So, I guess your pattern matching is difficult to use data structures
mixing Array and Hash.
I.e. "distinguish Array and Hash, and extract elements" seems difficult.

I expect Ruby's pattern matching support the programming style of
pattern matching of functional languages.
So, I'm suspicious with the deconstructing pattern of your proposal.

Note that I don't stick to "and" pattern.  


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

* Author: ktsj (Kazuki Tsujimoto)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
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>