From: Brian Candler Date: 2004-09-26T01:59:51+09:00 Subject: Re: Proc / def / yield semantics (long) 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.