From: Daniel Brockman Date: 2005-07-19T23:46:26+09:00 Subject: Re: [RCR] nil for unassigned keys Hi Simon, > Sometimes I find myself writing :key=>true, > it would be nice if one didn't had to do assignment, > so its value instead defaulted to nil. > > def test(hash={}) > p hash > end > > test('x', 'y'=>2, 'z') # {"x"=>nil, "y"=>2, "z"=>nil} Matz> In that case, how can we distinguish a hash key Matz> without value and ordinal mandatory argument? Theoretically, you could allow flag arguments *after* the first hash entry, def test(hash={}) ... end test :x => 2, :y, :z but then of course you wouldn't be able to pass flag arguments *only*. > Maybe I don't understand the mandatory argument thing? > But can't you just count them somehow? > > def test(arg1, arg2, hash={}) > puts "#{arg1.inspect} #{arg2.inspect} #{hash.inspect}" > end > > # mandatory argument? > test(1, 2) # 1 2 {} > > # hash key without value > test(1, 2, 42) # 1 2 {42=>nil} > test(1, 2, 3, 4) # 1 2 {3=>nil, 4=>nil} > test(1, 2, 3, 4=>5) # 1 2 {3=>nil, 4=>5} You can always do this: class Array def butlast(n=1) slice 0 ... -n end def to_option_hash if last.kind_of? Hash then hash = last ; flags = butlast else hash = {} ; flags = self end for flag in flags do flag.to_sym or raise ArgumentError, "cannot convert flag option `#{flag}' to symbol" hash[flag.to_sym] = true end hash end end def test(arg1, arg2, *args) options = args.to_option_hash puts "#{arg1.inspect} #{arg2.inspect} #{options.inspect}" end That reminds me, I promised I would write an RCR for allowing trailing argument after the splatting one, like so: def moomin(foo, *bar, baz) It was said that optional arguments should be allowed before the splat, but not after it. def moomin(foo, optional=nil, *bar, baz) In the above case of option hashes with flags, however, it is obvious that it would be useful to be able to have optional arguments after the splat. def test2(arg1, arg2, *flags, options={}) The above is unambiguous: we must always try to assign one argument to ‘options’ — anything else wouldn't make sense. It only becomes a problem when you have optional arguments both before *and* after the splat: def test3(arg1, arg2, arg3=nil, *flags, o={}) Obviously, when ‘test3’ is called with four arguments, we must assign the third to ‘arg3’ and the fourth to ‘options’, but what if only three arguments are given — does the last one go to ‘arg3’ or to ‘options’? I think the only reasonable thing to do is to fill optional argument slots from left to right, as is done now: def foo(a, b=nil, *c, d=nil, e) ... end foo 1 # ArgumentError foo 1, 2 # a=1, b=nil, c=[], d=nil, e=2 foo 1, 2, 3 # a=1, b=2, c=[], d=nil, e=3 foo 1, 2, 3, 4 # a=1, b=2, c=[], d=3, e=4 foo 1, 2, 3, 4, 5 # a=1, b=2, c=[3], d=4, e=5 This would also reasonably mean that mixing optional and mandatory arguments would become allowed: def bar(a, b=nil, c, d=nil, e) ... end foo 1 # ArgumentError foo 1, 2 # ArgumentError foo 1, 2, 3 # a=1, b=nil, c=2, d=nil, e=3 foo 1, 2, 3, 4 # a=1, b=2, c=3, d=nil, e=4 foo 1, 2, 3, 4, 5 # a=1, b=2, c=3, d=4, e=5 What do people think? Does this all seem reasonable? Eric Mahurin had another good example of a use case: def []= *coordinates, value -- Daniel Brockman So really, we all have to ask ourselves: Am I waiting for RMS to do this? --TTN. PS: Note how I've started putting parens in the argument lists of method definitions and leaving out the spaces around the equals signs for optional arguments. Somewhere, Guido is chuckling with satisfaction. I hope you are too.