Does Hash override Enumerable#map()?

时间秒杀一切 提交于 2020-01-23 11:39:48

问题


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 by Enumerable, how can Hash#map yield two variables to its block?

It doesn't. It yields 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 yielding, the extra arguments are bound to nil.

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!