Хэш переопределяет Enumerable # map ()? - PullRequest
3 голосов
/ 18 декабря 2011

Учитывая, что map() определяется Enumerable, как Hash#map yield может две переменные в своем блоке?Hash переопределяет Enumerable#map()?

Вот небольшой пример для развлечения:

ruby-1.9.2-p180 :001 > {"herp" => "derp"}.map{|k,v| k+v}
 => ["herpderp"] 

Ответы [ 2 ]

16 голосов
/ 18 декабря 2011

Карта не переопределяется

Hash.new.method(:map).owner # => Enumerable

Это дает две переменные, которые собираются в массив

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]]
9 голосов
/ 18 декабря 2011

Учитывая, что map() определяется как Enumerable, как может Hash#map yield две переменные в своем блоке?

Это не так.Это yield sa один объект в своем блоке, который представляет собой массив из двух элементов, состоящий из ключа и значения.

Это просто деструктурирующая привязка:

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]]

Теоретически, вам нужно было бы позвонить map следующим образом:

hsh.map {|(k, v)| ... }

И, фактически, для inject вам на самом деле нужно , чтобы сделать это:

hsh.inject {|acc, (k, v)| ... }

Однако Ruby более мягок в проверке аргументов для блоков, чем для методов.В частности:

  • Если вы yield более одного объекта, но блок принимает только один аргумент, все объекты собираются в массив.
  • Если вы yield один объект, но блок принимает несколько аргументов, Ruby выполняет деструктурирование связывания.(Это имеет место здесь.)
  • Если вы yield больше объектов, чем блок принимает аргументы, дополнительные объекты игнорируются.
  • Если у вас блок принимает больше аргументов, чем вы yield ing, дополнительные аргументы связаны с 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

Этот сумасшедший материал работает (в версии 1.8 и старше), потому что передача аргументов блока обрабатывается так же, как присваивание.IOW, хотя вышеуказанный блок пуст и не выполняет ничего , тот факт, что аргументы блока передаются так, как если бы они были назначены, означает, что установлен @a и установлен метод a.b=называется.Сумасшедший, а?Вот почему он был удален в 1.9.

Если вы хотите напугать своих коллег, прекратите определять ваши сеттеры следующим образом:

attr_writer :foo

и вместо этого определите их следующим образом:

define_method(:foo=) {|@foo|}

Просто убедитесь, что кто-то другой заканчивает тем, что поддерживает его: -)

...