问题
Given that map()
is defined by Enumerable
, how can Hash#map
yield
two variables to its block? Does Hash
override Enumerable#map()
?
Here's a little example, for fun:
ruby-1.9.2-p180 :001 > {"herp" => "derp"}.map{|k,v| k+v}
=> ["herpderp"]
回答1:
It doesn't override map
Hash.new.method(:map).owner # => Enumerable
It yields two variables which get collected into an array
class Nums
include Enumerable
def each
yield 1
yield 1, 2
yield 3, 4, 5
end
end
Nums.new.to_a # => [1, [1, 2], [3, 4, 5]]
回答2:
Given that
map()
is defined byEnumerable
, how canHash#map
yield
two variables to its block?
It doesn't. It yield
s a single object to its block, which is a two-element array consisting of the key and the value.
It's just destructuring bind:
def without_destructuring(a, b) end
without_destructuring([1, 2])
# ArgumentError: wrong number of arguments (1 for 2)
def with_destructuring((a, b)) end # Note the extra parentheses
with_destructuring([1, 2])
def with_nested_destructuring((a, (b, c))) p a; p b; p c end
with_nested_destructuring([1, [2, 3]])
# 1
# 2
# 3
# Note the similarity to
a, (b, c) = [1, [2, 3]]
Theoretically, you would have to call map
like this:
hsh.map {|(k, v)| ... }
And, in fact, for inject
, you actually need to do that:
hsh.inject {|acc, (k, v)| ... }
However, Ruby is more lenient with argument checking for blocks than it is for methods. In particular:
- If you
yield
more than one object, but the block only takes a single argument, all the objects are collected into an array. - If you
yield
a single object, but the block takes multiple arguments, Ruby performs destructuring bind. (This is the case here.) - If you
yield
more objects than the block takes arguments, the extra objects get ignored. - If you the block takes more arguments than you are
yield
ing, the extra arguments are bound tonil
.
Basically, the same semantics as parallel assignment.
In fact, before Ruby 1.9, block arguments actually did have assignment semantics. This allowed you to do crazy things like this:
class << (a = Object.new); attr_accessor :b end
def wtf; yield 1, 2 end
wtf {|@a, a.b| } # WTF? The block body is empty!
p @a
# 1
p a.b
# 2
This crazy stuff works (in 1.8 and older), because block argument passing is treated the same as assignment. IOW, even though the above block is empty and doesn't do anything, the fact that block arguments are passed as if they had been assigned, means that @a
is set and the a.b=
setter method is called. Crazy, huh? That's why it was removed in 1.9.
If you want to startle your co-workers, stop defining your setters like this:
attr_writer :foo
and instead define them like this:
define_method(:foo=) {|@foo|}
Just make sure someone else ends up maintaining it :-)
来源:https://stackoverflow.com/questions/8550232/does-hash-override-enumerablemap