Хэш "только для добавления" / "только для записи" в Ruby - PullRequest
4 голосов
/ 01 мая 2019

Я ищу своего рода "только для добавления" хэш, где ключи могут быть установлены только один раз.

Например:

capitals = AppendOnlyHash.new
capitals['france'] = 'paris'
capitals['japan'] = 'tokyo'
capitals['france'] = 'nice' # raises immutable exception

Какие-либо библиотечные рекомендации или идеи, как этого добиться?

(Вариант использования - это объект типа журналирования, который будет передан числовым классам со слабой связью и желающим определить, используют ли они тот же ключ.)

Ответы [ 3 ]

3 голосов
/ 01 мая 2019

Существует 10 методов, напрямую изменяющих хеш:

Hash.instance_methods.grep(/.+!\z/) << %i|[]= delete keep_if|
#⇒ [:select!, :filter!, :reject!, :compact!, delete, keep_if,
#   :transform_keys!, :transform_values!, :merge!, :[]=]

Кроме того, существует возможность изменить сами значения (capitals['france'] << ' and Lyon',), поэтому мы должны предотвратить этоа также.

class MyHash < Hash; end

MyHash.prepend(
  Module.new do
    (Hash.instance_methods.grep(/.+!\z/) | %i|delete keep_if|).each do |method|
      define_method(method) do |*args|
        raise "Method #{method} is restricted since it is mutating"
      end
    end
    def []=(key, val)
      raise "This hash is immutable" if key?(key)
      super(key, val.freeze) # to prevent inplace mutations
    end
  end
)

Необходимо извлечь из Hash, потому что в противном случае мы должны сломать все хэши.

Я не тестировал этот код, но он должен работать из коробки, (если нет, идея должна быть ясна.)

2 голосов
/ 01 мая 2019

Первая идея, я не учел ни одного недостатка:

class HashImmutable < Hash
  def []=(key,val)
    if self[key].frozen?
      super(key,val)
    else
      # self[key]
      raise 'Immutable'
    end
  end
end

hh = HashImmutable.new

hh[:france] = 'Paris'
hh[:italy] = 'Roma'
hh #=> {:france=>"Paris", :italy=>"Roma"}
hh[:italy] = 'Brescia'
#=> Immutable (RuntimeError)
1 голос
/ 01 мая 2019

Вот наивная попытка создать такой класс. Кажется, он отлично работает для «базового» использования:

class AppendOnlyHash < Hash
  def []=(key, value)
    raise "APPEND ONLY!!" if keys.include?(key)
    super
  end
end

Однако, это, безусловно, имеет некоторые недостатки.

Во-первых, что произойдет, если вы вызовете деструктивный метод для объекта, который пытается удалить некоторые ключи? Возможно, вы могли бы переопределить все такие методы - например, filter!, keep_if, delete, compact!, reject!, select!, transform_keys! и transform_values!. (Я что-нибудь пропустил? ...)

Тогда что делать с Hash#merge!? Я думаю, что это может быть обработано специально тоже; поскольку допустимо использовать , если , никакие ключи не переопределяются.

И, наконец, как вы можете гарантировать, что значения хеша "только для добавления" никогда не будут мутированными ? Учтите следующее:

capitals = AppendOnlyHash.new
str = "paris"
capitals['france'] = str
str << " CHANGED"

Вы можете вызывать .freeze для каждого значения, когда оно добавляется к хешу, но даже это не на 100% пуленепробиваемое - поскольку значение, в свою очередь, может быть другим Hash, которое подвержено тому же поведению.


Итак, в общем, я думаю, что это возможно через мою базовую реализацию, описанную выше, но я бы с осторожностью относился ко все более сложным крайним случаям, вызванным нежелательной мутацией "странными способами".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...