[ruby-core:117021] [Ruby master Feature#20318] Pattern matching `case ... in` support for triple-dot arguments

Issue #20318 has been reported by bradgessler (Brad Gessler). ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args: args, kwargs: kwargs, block: block } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by bradgessler (Brad Gessler). Description updated Add array example. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107085 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by rubyFeedback (robert heiler). Personally I find the double ... rather confusing. I understand the benefit of a more succint syntax - e. g. eliminating "(*args, **kwargs, &block)" - but even then I find the dual-triple-dot very strange. It seems rather "un-ruby" to me, however one wants to define that. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107086 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by ko1 (Koichi Sasada). I prefer ```ruby def foo(...) in [name] # foo('ko1') puts name in [first_name, last_name] # foo('ko1', 'ssd') puts "Hi there #{first_name} #{last_name}" in {greeting:} # foo(greeting: 'hello') puts "Hello #{greeting}" else puts "No match: #{args}" end ``` like ```ruby def foo BODY rescue RESCUE end ``` to represent method overloading. (but I understand many difficulties) ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107092 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by baweaver (Brandon Weaver). ko1 (Koichi Sasada) wrote in #note-3:
I prefer
...
to represent method overloading. (but I understand many difficulties)
Personally this is the syntax I would like to see, as I believe that the intention of this issue is method overloading, and it has been a fairly common request. I also agree that it is precedented with the `begin`less `rescue`. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107093 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by zverok (Victor Shepelev). @ko1 The pretty similar effect can be achieved by combining several recent features: ```ruby def foo(*, **) = case [*, **] in name, {} puts name in first_name, last_name, {} puts "Hi there #{first_name} #{last_name}" in [{greeting:}] puts "Hello #{greeting}" in *args puts "No match: #{args}" end foo('ko1') #=> "ko1" foo('ko1', 'ssd') #=> "Hi there ko1 ssd" foo(greeting: 'hello') #=> Hello hello foo('ko1', 'ssd', greeting: 'hello') #=> No match: ["ko1", "ssd", {:greeting=>"hello"}] ``` On the plus sides: * it doesn't require any new constructs but cleverly combines existing ones (that also can be combined in different ways, so it is insightful): e.g. `def foo(args) = statement` can become a popular idiom to emphasize one-statement methods,; * many of things are still explicit, if short: no special names, like `*args` is what you write there; a flexibility of applying this approach to either positional-only, or keyword-only, or both-arg-types methods (`*`/`**` can combined any necessary way); The drawbacks here are: * some things that feel unnecessary in this case (both positional and keyword args), like "don't forget empty `{}` at the end" or necessity to wrap `{greetings:}` in `[]` For methods that have only positional, or only keyword args, it would be a bit clearer: ```ruby def index(*) = case [*] in [Integer => index] p(index:) in [Integer => from, Integer => to] p(range: from..to) in [Range => range] p(range:) end index(1) #=> {:index=>1} index(2, 3) #=> {:range=>2..3} index(4..5) #=> {:range=>4..5} def hey(**) = case {**} in {rank:, **} puts "Salute, #{rank}" in first:, last: puts "Good day, #{first}, #{last}" in first: puts "Hey, #{first}" end hey(first: 'John') #=> "Hey, John" hey(first: 'John', last: 'Smith') #=> "Good day, John Smith" hey(rank: 'Major', first: 'John', last: 'Smith') #=> "Salute, Major" ``` It seem to look reasonably close to method reloading, but still has its quirks. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107094 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by nobu (Nobuyoshi Nakada). A patch for @ko1 style. Probably the code generation would be more efficient in compile.c. ```diff commit dcf97d47ad721bfbdae230234056df8a02044c7d Author: Nobuyoshi Nakada (nobu) <nobu@ruby-lang.org> AuthorDate: 2024-03-03 14:13:23 +0900 Commit: Nobuyoshi Nakada (nobu) <nobu@ruby-lang.org> CommitDate: 2024-03-03 14:13:23 +0900 [Feature #20318] Method dispatch per `in` diff --git a/parse.y b/parse.y index de90ee797ff..6623a5bffa0 100644 --- a/parse.y +++ b/parse.y @@ -1581,6 +1581,7 @@ static NODE *new_args_forward_call(struct parser_params*, NODE*, const YYLTYPE*, static int check_forwarding_args(struct parser_params*); static void add_forwarding_args(struct parser_params *p); static void forwarding_arg_check(struct parser_params *p, ID arg, ID all, const char *var); +static NODE *new_method_case_args(struct parser_params *p); static const struct vtable *dyna_push(struct parser_params *); static void dyna_pop(struct parser_params*, const struct vtable *); @@ -2740,7 +2741,7 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) %type <node> literal numeric simple_numeric ssym dsym symbol cpath %type <node_def_temp> defn_head defs_head k_def %type <node_exits> block_open k_while k_until k_for allow_exits -%type <node> top_compstmt top_stmts top_stmt begin_block endless_arg endless_command +%type <node> top_compstmt top_stmts top_stmt begin_block endless_arg endless_command method_body %type <node> bodystmt compstmt stmts stmt_or_begin stmt expr arg primary command command_call method_call %type <node> expr_value expr_value_do arg_value primary_value rel_expr %type <node_fcall> fcall @@ -2980,6 +2981,14 @@ bodystmt : compstmt[body] } ; +method_body : bodystmt + | p_case_body[body] + { + $$ = NEW_CASE3(new_method_case_args(p), $body, &@body); + /*% ripper: case!(Qnil, $:body) %*/ + } + ; + compstmt : stmts terms? { $$ = void_stmts(p, $1); @@ -4604,7 +4613,7 @@ primary : literal { push_end_expect_token_locations(p, &@head.beg_pos); } - bodystmt + method_body[bodystmt] k_end { restore_defun(p, $head); @@ -4619,7 +4628,7 @@ primary : literal { push_end_expect_token_locations(p, &@head.beg_pos); } - bodystmt + method_body[bodystmt] k_end { restore_defun(p, $head); @@ -15543,6 +15552,33 @@ new_args_forward_call(struct parser_params *p, NODE *leading, const YYLTYPE *loc return arg_blk_pass(args, block); } +static NODE * +new_method_case_args(struct parser_params *p) +{ + if (!local_id(p, idFWD_ALL)) { + compile_error(p, "not defined with ..."); + return Qnone; + } + + const YYLTYPE *loc = &NULL_LOC; + NODE *rest = NEW_LVAR(idFWD_REST, loc); +#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS + NODE *kwrest = NEW_LVAR(idFWD_KWREST, loc); +#endif + NODE *block = NEW_LVAR(idFWD_BLOCK, loc); + + NODE *args = NEW_SPLAT(rest, loc); +#ifndef FORWARD_ARGS_WITH_RUBY2_KEYWORDS + NODE *kwsplat = list_append(p, NEW_LIST(0, loc), kwrest); + args = NEW_ARGSPUSH(args, NEW_HASH(kwsplat, loc), loc); +#endif + args = NEW_ARGSCAT(args, NEW_SPLAT(block, loc), loc); + + NODE *cond = NEW_AND(NEW_CALL(rest, idEmptyP, 0, loc), + NEW_CALL(block, '!', 0, loc), loc); + return NEW_IF(cond, kwrest, args, loc); +} + static NODE * numparam_push(struct parser_params *p) { ``` ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107111 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by willcosgrove (Will Cosgrove). I'm going to throw my suggestion into the ring with no idea how hard it would be to implement. ``` ruby def foo when (name) # foo('ko1') puts name when (first_name, last_name) # foo('ko1', 'ssd') puts "Hi there #{first_name} #{last_name}" when (greeting:) # foo(greeting: 'hello') puts "Hello #{greeting}" else puts "No match: #{args}" end ``` I know there's a conflation happening in the pattern matching vocabulary with `when` vs `in`, but to me `when` reads clearer for method overloading. It reads to me as "foo, when called like this ..." I also think that being able to "pattern match" the same way you would in a method parameter list is more intuitive, at least to me, than the pattern matching syntax. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107138 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by ntl (Nathan Ladd). It might be a good idea to consider how proc/lambda/block arguments could also have comparable syntax. For instance, a block argument example: ``` ruby HTTP.post("http://example.com/some-resource", "some data") do |...| in code: (200...300) puts "Success!" in code: (300...400), location: puts "Redirect to #{location}" # post to location in code: (500...600) puts "Server error" do_retry end ``` ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107142 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by mame (Yusuke Endoh). I strongly feel "bad smell" for this proposal. In principle, one method should do one thing. But this syntax introduces a completly different branching depending on the arguments, which could encourage a weird method behavior. In fact, the motivating example prints different messages depending on the arguments. I believe such a method would never be recommended. In particular, the method that accepts `foo(str)` and `foo(greeting: str)` but not `foo(str, greeting: str)` looks rather weird to me. I am concerned that such a method could be so easily defined. Considering the Java overloading, each overloaded method definition would often delegate to the most common (receives the most arguments) definition. Given that, the motivating example should be: ```ruby def foo(...) case ... in args: [name] puts "Hello #{ name }" in args: [first_name, last_name] foo(first_name + " " + last_name) in kwargs: {greeting:} foo(greeting) else raise ArgumentError end end ```` I do not find this code easy to understand. As far as I know, Ruby has the most complicated argument system in the world. I don't think we should make it even more complicated. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107177 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/

Issue #20318 has been updated by matheusrich (Matheus Richard). @mame I understand the example is contrived, but even the core library does different things with different arguments. Let's take [`Random.rand`](https://docs.ruby-lang.org/en/3.3/Random.html#method-c-rand) as an example: ```rb Random.rand(10) # returns an Integer up to 10 Random.rand(10.0) # returns a Float up to 10 Random.rand(0..10) # returns an Integer between 0 and 10 Random.rand(0..10.0) # returns a Float between 0 and 10 `` ` The logic to do this is [spread out in the method definition with several if/else](https://github.com/ruby/ruby/blob/v3_3_0/random.c#L1549). This proposal could streamline those types of interfaces. Granted, this can currently be achieved with the approach @zverok mentioned. ---------------------------------------- Feature #20318: Pattern matching `case ... in` support for triple-dot arguments https://bugs.ruby-lang.org/issues/20318#change-107195 * Author: bradgessler (Brad Gessler) * Status: Open ---------------------------------------- # Premise Sometimes when I'm creating a method for an API, I'd like to do pattern matching against the arguments. Today I have to do something like this: ```ruby def foo(*args, **kwargs, &block) case { args:, kwargs:, block: } in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` Or an array like this: ```ruby def bar(*args, **kwargs, &block) case [args, kwargs, block] in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` # Proposal I'd like to propose the same thing, but for `...`, like this: ```ruby def foo(...) case ... in args: [name] puts name in args: [first_name, last_name] puts "Hi there #{first_name} #{last_name}" in kwargs: {greeting:} puts "Hello #{greeting}" else puts "No match: #{args}" end end foo "Hi" foo "Brad", "Gessler" foo greeting: "Brad" ``` One thing I'm not sure sure about: the `args`, `kwargs`, and `block` names appear out of thin air, so ideally those could somehow be named or have a syntax that doesn't require those names. The array would look like this: ```ruby def bar(...) case ... in [name], {}, nil puts name in [first_name, last_name], {}, nil puts "Hi there #{first_name} #{last_name}" in [], {greeting:}, nil puts "Hello #{greeting}" else puts "No match: #{args}, #{kwargs}" end end bar "Howdy" bar "Bradley", "Gessler" bar greeting: "Bradley" ``` -- https://bugs.ruby-lang.org/
participants (10)
-
baweaver (Brandon Weaver)
-
bradgessler (Brad Gessler)
-
ko1 (Koichi Sasada)
-
mame (Yusuke Endoh)
-
matheusrich (Matheus Richard)
-
nobu (Nobuyoshi Nakada)
-
ntl (Nathan Ladd)
-
rubyFeedback (robert heiler)
-
willcosgrove (Will Cosgrove)
-
zverok (Victor Shepelev)