From: Marc-Andre Lafortune Date: 2011-12-22T13:55:15+09:00 Subject: [ruby-core:41772] Re: [ruby-trunk - Feature #5474][Assigned] keyword argument Hi, Not sure why the following modifications made in redmine were not posted on the mailing list... While having fun testing your patch, I encountered an issue with more than 2 rest arguments: def foo(*rest, b: 0, **options) [rest, options] end foo(1, 2, bar: 0) # => [[1, 2], {bar: 0}] OK foo(1, 2, 3, bar: 0) # => [[1, 2, {bar: 0}], {bar: 0}] Not OK On 30 October 2011 11:10, Yusuke Endoh wrote: > Currently, my patch allows ** only when there are one or more > keyword arguments. > > This is because I didn't think of any use case. > In addition, I wanted to simplify the implementation of parser. > (Unfortunately, adding a new argument type requries *doubling* > the parser rules to avoid yacc's conflict) > Do you think we need it? I'm worried about cases where one doesn't use named arguments directly but wants to pass them on to another method. Let's say we have a Gem that defines: def import(*files, format: :auto_detect, encoding: "utf-8") # ... end Say one wants to implement a variation of this that prints out a progression. If it was possible to have a ** arg, one could: def my_import(*files, **options) puts "Importing #{files.size} files: #{files}" import(*files, options) end A new version of the gem can modify the method `import` by adding options, or changing the default of any option, add options and `my_import` would work perfectly. But if we can't define it this way, we have to do some artificial hoop jumping. Either: def my_import(*files, format: :auto_detect, encoding: "utf-8") puts "Importing #{files.size} files: #{files}..." import(*files, format: format, encoding: encoding)# ... end Downsides: verbose, won't pass on any new options, default changes won't be reflected def my_import(*files, format: :auto_detect, **options) puts "Importing #{files.size} files: #{files}" import(*files, options.merge(format: format))# ... end Downsides: verbose, format looks like a special option and if its default changes in the Gem, it won't be reflected, but others will def my_import(*args) files = args.dup files.pop if files.last.respond_to?(:to_hash) puts "Importing #{files.size} files: #{files}" import(*args) end Downside: `*args` less clear then `*files, **options`, slower, verbose, ... I feel that the first (currently illegal) version is the much nicer than the alternatives. > I didn't implement caller's **. > I wonder if we need it or not. Is "other(a, h)" not enough? I think one reason to have it is to avoid calling Hash#merge when combining options, like in the above examples. Instead of def foo(bar: 42, **options) baz(extra_option: 1, **options) end Currently, one has to do: def foo(bar: 42, **options) baz(options.merge(extra_option: 1)) end Benoit Daloze wrote: > 2) I'm a bit dubious about the `**h` syntax to get (and I guess to pass) a Hash though. > ... > Something related to `{}`, the literal Hash syntax, would fit better in my opinion. > > Do you have any idea of an alternate syntax to `**h` ? I haven't given much thought, but here's an alternate suggestion, where **h => {*h}: def foo(a, b=1, *rest, {c: 2, d: 3, *options}) end If the parser allows, the {} could be optional, at least in the case without a "hash-rest" argument. This could actually be two new concepts that could be used everywhere in Ruby (not just for argument passing): a) Splat inside a hash does a merge, e.g. h = {foo: 1, bar: 2} {*h, baz: 3} # => {foo: 1, bar: 2, baz: 3} I'm not sure if it should be silent in case of duplicate key (like `Hash#merge` with no block) or if it should raise an error. b) Hash destructuring, e.g.: h = {foo: 1, bar: 2} {foo: 42, extra: 0, *rest} = h foo # => 1 extra # => 0 rest # => {bar: 2} It would be nice to not need a default: {foo, extra, *rest} = h extra # => nil This could be allowed in arguments too: def foo(a, b=1, *rest, {c, d, *options}) end What do you think?