mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Pattern matching is no longer experimental
This commit is contained in:
parent
4f8d9b0db8
commit
b601532411
4 changed files with 46 additions and 64 deletions
18
NEWS.md
18
NEWS.md
|
|
@ -48,7 +48,23 @@ sufficient information, see the ChangeLog file or Redmine
|
||||||
instead of a warning. yield in a class definition outside of a method
|
instead of a warning. yield in a class definition outside of a method
|
||||||
is now a SyntaxError instead of a LocalJumpError. [[Feature #15575]]
|
is now a SyntaxError instead of a LocalJumpError. [[Feature #15575]]
|
||||||
|
|
||||||
* Find pattern is added. [[Feature #16828]]
|
* Pattern matching is no longer experimental. [[Feature #17260]]
|
||||||
|
|
||||||
|
* One-line pattern matching now uses `=>` instead of `in`. [EXPERIMENTAL]
|
||||||
|
[[Feature #17260]]
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# version 3.0
|
||||||
|
{a: 0, b: 1} => {a:}
|
||||||
|
p a # => 0
|
||||||
|
|
||||||
|
# version 2.7
|
||||||
|
{a: 0, b: 1} in {a:}
|
||||||
|
p a # => 0
|
||||||
|
```
|
||||||
|
|
||||||
|
* Find pattern is added. [EXPERIMENTAL]
|
||||||
|
[[Feature #16828]]
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
|
case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
= Pattern matching
|
= Pattern matching
|
||||||
|
|
||||||
Pattern matching is an experimental feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables.
|
Pattern matching is a feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables.
|
||||||
|
|
||||||
Pattern matching in Ruby is implemented with the +in+ operator, which can be used in a standalone expression:
|
Pattern matching in Ruby is implemented with the +case+/+in+ expression:
|
||||||
|
|
||||||
<expression> in <pattern>
|
|
||||||
|
|
||||||
or within the +case+ statement:
|
|
||||||
|
|
||||||
case <expression>
|
case <expression>
|
||||||
in <pattern1>
|
in <pattern1>
|
||||||
|
|
@ -19,11 +15,15 @@ or within the +case+ statement:
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
|
||||||
(Note that +in+ and +when+ branches can *not* be mixed in one +case+ statement.)
|
or with the +=>+ operator, which can be used in a standalone expression:
|
||||||
|
|
||||||
Pattern matching is _exhaustive_: if variable doesn't match pattern (in a separate +in+ statement), or doesn't matches any branch of +case+ statement (and +else+ branch is absent), +NoMatchingPatternError+ is raised.
|
<expression> => <pattern>
|
||||||
|
|
||||||
Therefore, +case+ statement might be used for conditional matching and unpacking:
|
(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.)
|
||||||
|
|
||||||
|
Pattern matching is _exhaustive_: if variable doesn't match pattern (in a separate +in+ clause), or doesn't matches any branch of +case+ expression (and +else+ branch is absent), +NoMatchingPatternError+ is raised.
|
||||||
|
|
||||||
|
Therefore, +case+ expression might be used for conditional matching and unpacking:
|
||||||
|
|
||||||
config = {db: {user: 'admin', password: 'abc123'}}
|
config = {db: {user: 'admin', password: 'abc123'}}
|
||||||
|
|
||||||
|
|
@ -37,11 +37,11 @@ Therefore, +case+ statement might be used for conditional matching and unpacking
|
||||||
end
|
end
|
||||||
# Prints: "Connect with user 'admin'"
|
# Prints: "Connect with user 'admin'"
|
||||||
|
|
||||||
whilst standalone +in+ statement is most useful when expected data structure is known beforehand, to just unpack parts of it:
|
whilst the +=>+ operator is most useful when expected data structure is known beforehand, to just unpack parts of it:
|
||||||
|
|
||||||
config = {db: {user: 'admin', password: 'abc123'}}
|
config = {db: {user: 'admin', password: 'abc123'}}
|
||||||
|
|
||||||
config in {db: {user:}} # will raise if the config's structure is unexpected
|
config => {db: {user:}} # will raise if the config's structure is unexpected
|
||||||
|
|
||||||
puts "Connect with user '#{user}'"
|
puts "Connect with user '#{user}'"
|
||||||
# Prints: "Connect with user 'admin'"
|
# Prints: "Connect with user 'admin'"
|
||||||
|
|
@ -113,7 +113,7 @@ Both array and hash patterns support "rest" specification:
|
||||||
end
|
end
|
||||||
#=> "matched"
|
#=> "matched"
|
||||||
|
|
||||||
In +case+ (but not in standalone +in+) statement, parentheses around both kinds of patterns could be omitted
|
In +case+ (but not in +=>+) expression, parentheses around both kinds of patterns could be omitted
|
||||||
|
|
||||||
case [1, 2]
|
case [1, 2]
|
||||||
in Integer, Integer
|
in Integer, Integer
|
||||||
|
|
@ -378,53 +378,23 @@ Additionally, when matching custom classes, expected class could be specified as
|
||||||
|
|
||||||
== Current feature status
|
== Current feature status
|
||||||
|
|
||||||
As of Ruby 2.7, feature is considered _experimental_: its syntax can change in the future, and the performance is not optimized yet. Every time you use pattern matching in code, the warning will be printed:
|
As of Ruby 3.0, one-line pattern matching and find pattern are considered _experimental_: its syntax can change in the future. Every time you use these features in code, the warning will be printed:
|
||||||
|
|
||||||
{a: 1, b: 2} in {a:}
|
[0] => [*, 0, *]
|
||||||
# warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!
|
# warning: Find pattern is experimental, and the behavior may change in future versions of Ruby!
|
||||||
|
# warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!
|
||||||
|
|
||||||
To suppress this warning, one may use newly introduced Warning::[]= method:
|
To suppress this warning, one may use newly introduced Warning::[]= method:
|
||||||
|
|
||||||
Warning[:experimental] = false
|
Warning[:experimental] = false
|
||||||
eval('{a: 1, b: 2} in {a:}')
|
eval('[0] => [*, 0, *]')
|
||||||
# ...no warning printed...
|
# ...no warning printed...
|
||||||
|
|
||||||
Note that pattern-matching warning is raised at a compile time, so this will not suppress warning:
|
Note that pattern-matching warning is raised at a compile time, so this will not suppress warning:
|
||||||
|
|
||||||
Warning[:experimental] = false # At the time this line is evaluated, the parsing happened and warning emitted
|
Warning[:experimental] = false # At the time this line is evaluated, the parsing happened and warning emitted
|
||||||
{a: 1, b: 2} in {a:}
|
[0] => [*, 0, *]
|
||||||
|
|
||||||
So, only subsequently loaded files or `eval`-ed code is affected by switching the flag.
|
So, only subsequently loaded files or `eval`-ed code is affected by switching the flag.
|
||||||
|
|
||||||
Alternatively, command-line key <code>-W:no-experimental</code> can be used to turn off "experimental" feature warnings.
|
Alternatively, command-line key <code>-W:no-experimental</code> can be used to turn off "experimental" feature warnings.
|
||||||
|
|
||||||
One of the things developer should be aware of, which probably to be fixed in the upcoming versions, is that pattern matching statement rewrites mentioned local variables on partial match, <i>even if the whole pattern is not matched</i>.
|
|
||||||
|
|
||||||
a = 5
|
|
||||||
case [1, 2]
|
|
||||||
in String => a, String
|
|
||||||
"matched"
|
|
||||||
else
|
|
||||||
"not matched"
|
|
||||||
end
|
|
||||||
#=> "not matched"
|
|
||||||
a
|
|
||||||
#=> 5 -- even partial match not happened, a is not rewritten
|
|
||||||
|
|
||||||
case [1, 2]
|
|
||||||
in a, String
|
|
||||||
"matched"
|
|
||||||
else
|
|
||||||
"not matched"
|
|
||||||
end
|
|
||||||
#=> "not matched"
|
|
||||||
a
|
|
||||||
#=> 1 -- the whole pattern not matched, but partial match happened, a is rewritten
|
|
||||||
|
|
||||||
Currently, the only core class implementing +deconstruct+ and +deconstruct_keys+ is Struct.
|
|
||||||
|
|
||||||
Point = Struct.new(:x, :y)
|
|
||||||
Point[1, 2] in [a, b]
|
|
||||||
# successful match
|
|
||||||
Point[1, 2] in {x:, y:}
|
|
||||||
# successful match
|
|
||||||
|
|
|
||||||
22
parse.y
22
parse.y
|
|
@ -502,7 +502,6 @@ static NODE *new_find_pattern(struct parser_params *p, NODE *constant, NODE *fnd
|
||||||
static NODE *new_find_pattern_tail(struct parser_params *p, ID pre_rest_arg, NODE *args, ID post_rest_arg, const YYLTYPE *loc);
|
static NODE *new_find_pattern_tail(struct parser_params *p, ID pre_rest_arg, NODE *args, ID post_rest_arg, const YYLTYPE *loc);
|
||||||
static NODE *new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc);
|
static NODE *new_hash_pattern(struct parser_params *p, NODE *constant, NODE *hshptn, const YYLTYPE *loc);
|
||||||
static NODE *new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc);
|
static NODE *new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, const YYLTYPE *loc);
|
||||||
static NODE *new_case3(struct parser_params *p, NODE *val, NODE *pat, const YYLTYPE *loc);
|
|
||||||
|
|
||||||
static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc);
|
static NODE *new_kw_arg(struct parser_params *p, NODE *k, const YYLTYPE *loc);
|
||||||
static NODE *args_with_numbered(struct parser_params*,NODE*,int);
|
static NODE *args_with_numbered(struct parser_params*,NODE*,int);
|
||||||
|
|
@ -1661,7 +1660,11 @@ expr : command_call
|
||||||
{
|
{
|
||||||
p->ctxt.in_kwarg = $<ctxt>3.in_kwarg;
|
p->ctxt.in_kwarg = $<ctxt>3.in_kwarg;
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = new_case3(p, $1, NEW_IN($5, 0, 0, &@5), &@$);
|
$$ = NEW_CASE3($1, NEW_IN($5, 0, 0, &@5), &@$);
|
||||||
|
|
||||||
|
if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL))
|
||||||
|
rb_warn0L(nd_line($$), "One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!");
|
||||||
|
|
||||||
/*% %*/
|
/*% %*/
|
||||||
/*% ripper: case!($1, in!($5, Qnil, Qnil)) %*/
|
/*% ripper: case!($1, in!($5, Qnil, Qnil)) %*/
|
||||||
}
|
}
|
||||||
|
|
@ -2998,7 +3001,7 @@ primary : literal
|
||||||
k_end
|
k_end
|
||||||
{
|
{
|
||||||
/*%%%*/
|
/*%%%*/
|
||||||
$$ = new_case3(p, $2, $4, &@$);
|
$$ = NEW_CASE3($2, $4, &@$);
|
||||||
/*% %*/
|
/*% %*/
|
||||||
/*% ripper: case!($2, $4) %*/
|
/*% ripper: case!($2, $4) %*/
|
||||||
}
|
}
|
||||||
|
|
@ -4176,6 +4179,9 @@ p_args_tail : p_rest
|
||||||
p_find : p_rest ',' p_args_post ',' p_rest
|
p_find : p_rest ',' p_args_post ',' p_rest
|
||||||
{
|
{
|
||||||
$$ = new_find_pattern_tail(p, $1, $3, $5, &@$);
|
$$ = new_find_pattern_tail(p, $1, $3, $5, &@$);
|
||||||
|
|
||||||
|
if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL))
|
||||||
|
rb_warn0L(nd_line($$), "Find pattern is experimental, and the behavior may change in future versions of Ruby!");
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -11679,16 +11685,6 @@ new_hash_pattern_tail(struct parser_params *p, NODE *kw_args, ID kw_rest_arg, co
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NODE *
|
|
||||||
new_case3(struct parser_params *p, NODE *val, NODE *pat, const YYLTYPE *loc)
|
|
||||||
{
|
|
||||||
NODE *node = NEW_CASE3(val, pat, loc);
|
|
||||||
|
|
||||||
if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL))
|
|
||||||
rb_warn0L(nd_line(node), "Pattern matching is experimental, and the behavior may change in future versions of Ruby!");
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
static NODE*
|
static NODE*
|
||||||
dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
|
dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1473,13 +1473,13 @@ END
|
||||||
assert_warn('') {eval(code)}
|
assert_warn('') {eval(code)}
|
||||||
|
|
||||||
Warning[:experimental] = true
|
Warning[:experimental] = true
|
||||||
assert_warn(/Pattern matching is experimental/) {eval(code)}
|
assert_warn(/is experimental/) {eval(code)}
|
||||||
ensure
|
ensure
|
||||||
Warning[:experimental] = w
|
Warning[:experimental] = w
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_experimental_warning
|
def test_experimental_warning
|
||||||
assert_experimental_warning("case 0; in 0; end")
|
assert_experimental_warning("case [0]; in [*, 0, *]; end")
|
||||||
assert_experimental_warning("0 => 0")
|
assert_experimental_warning("0 => 0")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue