既然map()定义了Enumerable,Hash#map yield两个变量怎么能阻止?是否Hash覆盖Enumerable#map()?
这是一个小例子,为了好玩:
ruby-1.9.2-p180 :001 > {"herp" => "derp"}.map{|k,v| k+v}
=> ["herpderp"]
Run Code Online (Sandbox Code Playgroud)
Jos*_*eek 16
它不会覆盖地图
Hash.new.method(:map).owner # => Enumerable
Run Code Online (Sandbox Code Playgroud)
它产生两个变量,这些变量被收集到一个数组中
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]]
Run Code Online (Sandbox Code Playgroud)
既然
map()定义了Enumerable,Hash#mapyield两个变量怎么能阻止?
它没有.它yield是块的单个对象,它是一个由元素和值组成的双元素数组.
它只是解构绑定:
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]]
Run Code Online (Sandbox Code Playgroud)
从理论上讲,你必须这样打电话map:
hsh.map {|(k, v)| ... }
Run Code Online (Sandbox Code Playgroud)
事实上,inject实际上,你需要这样做:
hsh.inject {|acc, (k, v)| ... }
Run Code Online (Sandbox Code Playgroud)
但是,Ruby对块的参数检查比对方法更宽松.特别是:
yield多个对象,但该块只接受一个参数,则所有对象都会被收集到一个数组中.yield是单个对象,但该块需要多个参数,则Ruby会执行解构绑定.(这是这种情况.)yield对象多于块接受参数,则会忽略额外的对象.yield使用的参数,则额外的参数必然会被绑定nil.基本上,与并行赋值相同的语义.
实际上,在Ruby 1.9之前,块参数实际上确实具有赋值语义.这让你做了这样的疯狂事情:
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
Run Code Online (Sandbox Code Playgroud)
这个疯狂的东西有效(在1.8及更早版本中),因为块参数传递被视为与赋值相同.IOW,即使上面的块是空的并且没有做任何事情,块参数的传递就好像它们已经被赋值一样,这意味着@a设置并a.b=调用了setter方法.疯了,是吗?这就是它在1.9中删除的原因.
如果你想让你的同事吃惊,那就停止定义你的setter:
attr_writer :foo
Run Code Online (Sandbox Code Playgroud)
而是像这样定义它们:
define_method(:foo=) {|@foo|}
Run Code Online (Sandbox Code Playgroud)
只要确保别人最终维护它:-)