Почему Hash # merge использует оператор splat, возвращающий Массив Массивов вместо Массива Хешей? - PullRequest
4 голосов
/ 14 марта 2019

TL; DR

Я решил проблему методом проб и ошибок, но в моем понимании есть пробел в том, как оператор splat и метод pp постоянно дают мне объект, отличный от того, который ядумаю у меня есть.Я хотел бы понять этот пробел и определить лучший способ объединения массива хэшей.Я также хотел бы иметь возможность более эффективно отлаживать подобные вещи в будущем.

Примеры кода и этапы отладки являются первыми.Мое полуудовлетворительное решение и более подробный вопрос приведены внизу.

Код

Я использую MRI Ruby 2.6.2.Учитывая класс Foo, я ожидаю, что окна Foo # вернут объединенный хеш.Вот минимальный пример класса:

class Foo
  attr_reader :windows

  def initialize
    @windows = {}
  end

  def pry
    { pry: "stuff pry\r" }
  end 

  def irb
    { irb: "stuff irb\r" }
  end 

  def windows= *hashes
    @windows.merge! *hashes
  end
end

foo = Foo.new
foo.windows = foo.pry, foo.irb

Проблема (с отладкой)

Тем не менее, пытается присвоить foo.windows (или даже пытается быть менее двусмысленным с помощьюиз синтаксического анализатора с foo.windows= foo.pry, foo.irb) Я получаю исключение из REPL:

TypeError: нет неявного преобразования массива в хэш

Однако, если я изменяю экземплярс одноэлементным методом для захвата значения аргумента *hashes я вижу массив хэшей, который я могу просто слить.Рассмотрим следующее:

def foo.windows= *hashes
  pp *hashes
end
foo.windows = foo.pry, foo.irb
#=> [{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]

{}.merge *[{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]
#=> {:pry=>"stuff pry\r", :irb=>"stuff irb\r"}

Получение вывода из #pp дает мне что-то, что работает, как и ожидалось.И все же, когда я копаю немного глубже, оказывается, что что-то накладывается на дополнительное вложение хэша:

def foo.windows= *hashes
  pp *hashes.inspect
end
foo.windows = foo.pry, foo.irb
"[[{:pry=>\"stuff pry\\r\"}, {:irb=>\"stuff irb\\r\"}]]"

Даже если возвращаемое значение не 'Чтобы показать это, есть дополнительный набор квадратных скобок, заставляющий массив быть вложенным.Я не совсем понимаю, откуда они берутся.

Что работает

Итак, по какой-то причине мне нужно разделить массив, сгладить его, и тогда я смогуСлияние:

def foo.windows= *hashes
  @windows.merge! *hashes.flatten
end

# The method still returns unexpected results here, but...
foo.windows = foo.pry, foo.irb
#=> [{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]

# the value stored in the instance variable is finally correct!
foo.windows
#=> {:pry=>"stuff pry\r", :irb=>"stuff irb\r"}

Но почему?

Так что да, мне удалось решить проблему.Тем не менее, мой вопрос на самом деле о , почему объединение хэшей не работает должным образом, и откуда появляется дополнительный слой вложенности.Я не ожидаю Массив Хэшей, а скорее Массив Хэшей.Есть ли пробел в моем понимании, или это какой-то странный крайний случай?

Что более важно, почему это так сложно отладить?Я бы ожидал, что #pp или #inspect покажет мне объект, который у меня действительно есть, а не Array of Hashes в качестве возвращаемого значения, когда у меня явно будет Array of Arrays, содержащий Hash.

Ответы [ 2 ]

3 голосов
/ 14 марта 2019

Прежде всего, *hashes может означать две совершенно разные вещи:

  • def windows=(*hashes) означает «Сохранить все аргументы, переданные этому методу, в массив hashes в порядке появления.»
  • , тогда как @windows.merge! *hashes использует элементы hashes в качестве отдельных аргументов вызова метода merge!.

Однако, если у вас есть метод присвоения, , перечисляющий несколько значений, автоматически создает массив:

Вы можете неявно создать массив, перечислив несколько значений при назначении:

a = 1, 2, 3
p a # prints [1, 2, 3]

Таким образом foo.windows(foo.pry, foo.irb) с

def windows(*hashes)
    pp hashes
    @windows.merge! *hashes
end

напечатает [{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}], как и ожидалось. Но так как у вас есть метод назначения, вы должны просто удалить звездочку из вашего определения.

def windows=(hashes)
  @windows.merge!(*hashes)
end
2 голосов
/ 14 марта 2019

Что вам не хватает, так это то, что синтаксический анализатор Ruby не допускает методы установки с более чем одним параметром .

Когда вы передаете множеству параметров в сеттер, они автоматически объединяются в массив (потому что a = 1, 2 означает то же самое, что и a = [1, 2]):

def foo.a=(values)
  pp values
end

foo.a = 1, 2 # [1, 2]

Однако, если вы определите аргумент splat, так как этот массив считается единственным аргументом, это происходит:

def foo.a=(*values)
  pp values
end

foo.a = 1, 2 # [[1, 2]]
...