Ruby 中的 "append-only" / "write-only" 哈希



我正在寻找一种"仅追加"哈希,其中键只能设置一次。

例如:

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

任何图书馆建议或想法如何实现这一目标?

(用例是一个日志记录类型对象,该对象将被传递给松散连接的类,并希望检测是否有人使用相同的键。

有 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_ifdeletecompact!reject!select!transform_keys!transform_values!。(我错过了?...)

那么,如何处理Hash#merge!?我想这也可以特别处理;因为如果要重新定义 no 键,则使用它是有效的。

最后,如何确保"仅追加"哈希值永远不会发生变化?请考虑以下事项:

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

您可以在每个值添加到哈希值时调用.freeze,但即使这样也不是 100% 防弹的 - 因为该值可能反过来是另一个Hash,容易受到相同行为的影响。


所以总而言之,我认为通过我上面的基本实现这是可能的,但我会谨慎对待由反对突变以"奇怪方式"引起的日益复杂的边缘情况。

最新更新