Ruby: 初始化一个散列:分配值,引发未定义的键访问,然后冻结,一次完成?

Ruby: Initializing a hash: Assign values, throw on undefined key access, and freeze, all at once?

在 Ruby 中,我想初始化一个新的散列,这样:

是否有一种优雅的 Ruby 风格的方式一次性完成此设置?

我知道这可以分三行完成,例如:

COIN_SIDES = { heads: 'heads', tails: 'tails' }
COIN_SIDES.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" }
COIN_SIDES.freeze

我不确定这是否非常优雅,但在一行(长)行中实现此目的的一种方法是使用 .tap:

COIN_SIDES = { heads: 'heads', tails: 'tails' }.tap { |cs| cs.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" } }.tap(&:freeze)

这种方法至少避免了 RuboCop: Freeze mutable objects assigned to constants [Style/MutableConstant] warning generated when running the RuboCop 上面原始问题中代码的 3 行版本的 linter。

您可以通过使用 default_proc 初始化散列然后使用合并添加组件来完成此操作!:

h = Hash.new{|hash, key| raise KeyError, "Key '#{key}' not found"}.merge!({ heads: 'heads', tails: 'tails' }).freeze

您可以通过自定义 class 来完成大部分功能,唯一的缺点是它不是真正的哈希,因此您需要明确添加额外的功能,例如 .keyseach,如果需要的话:

class HashLike
  def initialize(hsh)
    singleton_class.attr_reader *hsh.keys
    hsh.each { |k,v| instance_variable_set "@#{k}", v }
  end
end

hashlike = HashLike.new(some_value: 1)
hashlike.some_value # 1
hashlike.missing_value # NoMethodError
hashlike.some_value = 2 # NoMethodError

另一种类似的方式:

class HashLike2
  def initialize(hsh)
    @hsh = hsh
  end

  def [](key)
    @hsh.fetch(key)
  end
end

hashlike2 = HashLike2.new(some_value: 1)
hashlike2[:some_value] # 1
hashlike2[:missing_value] # KeyError
hashlike2[:some_value] = 2 # NoMethodError

但在我看来,没有太多理由这样做。您可以轻松地将原来的 3 行移动到某个地方的方法中,然后无论是 3 行还是 1 行都没有关系。