I support all that you have written, as I have been bitten by this
inconsistency in the past (in a less serious way than you).
Can I add some simple observations:
(1) When you write a,b = c
then Ruby has to assume you mean either:
(i) a,b = c,nil
or (ii) a,b = *c
In the simple assignment case, both can be achieved explicitly; I guess Ruby
chooses (ii) as "more likely to be what you meant", but you don't actually
need that magic because you can achieve it youself using "*". Similarly for
a = b,c:
(iii) a,dummy = b,c
(iv) *a = b,c
(2) The problem with Hash#each is that for hashes to be enumerable they must
contain a list of individual "things", and a "thing" is chosen here to be a
[key,value] pair.
So Hash#each does yield [k,v]
and the block we pass in takes |thing| as a parameter. Now, we'd like to be
able to do
myhash.each { |key,value| ... }
but we need magic for that. We can't achieve it using "*", because we'd have
to change the 'yield' statement to do that: yield *[k,v], or yield k,v. In
other words, we'd have to modify the source code of the iterator itself.
NOW: if it's *only* Hash#each which is the problem here, then one Ruby-2.0
solution is to add a new iterator to Hash, or just redefine
Hash#each_pair --> yield k,v
This iterator already exists in the language, so you could make your code
backwards-compatibile by using it where you want to do { |key,value| ... }
But it seems to me a better option would be to have a way at the *receiver*
of specifying that you want the argument 'exploded', and it turns out that
Ruby already has this:
myhash.each { |(key,value)| ... }
Tested in 1.8.1:
def foo
yield [1,2], [3,4]
end
foo { |a,b| p a,b }
[1, 2]
[3, 4]
=> nil
foo { |x,(y,z)| p x,y,z }
[1, 2]
3
4
=> nil
So perhaps all we need to do, for ruby-2.0 at least, is keep this mechanism
and get rid of the rule which says "if single argument is an array then
auto-expand it" completely.
And then, maybe we should persuade people to write
(a,b)=c
rather than
a,b=*c
(although exploding at the sender, i.e. "yield *foo" and "bar(*foo)", are
still useful constructs)
Applying this to your example:
> There are two ways that the discrepancy could be resolved: either
> the behavior of yield could be changed, or that of Proc#call. If the
> former path had been taken
...
> the prior behavior could have been reproduced (if desired) by writing:
>
> def foo
> yield(*[:key, :value])
> end
> foo {|k, v| p [k, v]} #[:key, :value]
Without changing the definition of foo, you could just have called it as
foo {|(k,v)| p [k, v]} #[:key, :value]
(3) It may be possible to keep Ruby's automagic exploding of arguments, if
we limit it to cases where there is no ambiguity. I am thinking of:
- the sender passes a single array value;
- the receiver REQUIRES two or more arguments.
This is unlike def foo(head,*rest) or proc {|head,*rest| ... }, where it can
take _one_ or more arguments, so the rule would not apply.
This might be a solution for 1.8.2, where you want to be backwards
compatible. At least, Hash#each { |key,value| ... } would continue to work.
But it still wouldn't be consistent, unless you also applied it also to
method calls (which would be dubious, IMO)
Regards,
Brian.