Хэш "только для добавления" / "только для записи" в Ruby
Я ищу своего рода "только для добавления" хэш, где ключи могут быть установлены только один раз.
Например:
capitals = AppendOnlyHash.new
capitals['france'] = 'paris'
capitals['japan'] = 'tokyo'
capitals['france'] = 'nice' # raises immutable exception
Какие-нибудь библиотечные рекомендации или идеи, как этого добиться?
(Вариант использования - это объект типа журналирования, который будет передан числовым классам со слабой связью и желающим определить, используют ли они тот же ключ.)
3 ответа
Есть 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
потому что иначе мы должны сломать все хеши.
Я не тестировал этот код, но он должен работать из коробки (если нет, идея должна быть ясной).
Первой идеей я не учел ни одного недостатка:
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)
Вот наивная попытка создать такой класс. Кажется, работает нормально для "базового" использования:
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
, который подвержен такому же поведению.
Итак, в целом, я думаю, что это возможно через мою базовую реализацию, описанную выше, но я бы с осторожностью относился ко все более сложным крайним случаям, вызванным нежелательной мутацией "странными способами".