0

Ruby 通过将块传递给以下内容来支持哈希的自动生存Hash.new

hash = Hash.new { |h, k| h[k] = 42 }
hash[:foo] += 1   # => 43

我也想为结构实现自动生存。这是我能想到的最好的:

Foo = Struct.new(:bar) do
  def bar
    self[:bar] ||= 42
  end
end

foo = Foo.new
foo.bar += 1   # => 43

当然,这只会自动激活命名访问器 ( foo.bar),而不是[]表单 ( foo[:bar])。有没有更好的方法来实现结构的自动激活,特别是一种对foo.barfoo[:bar]形式都有效的方法?

4

1 回答 1

3

我会采用以下方法:

module StructVivificator
  def self.prepended(base)
    base.send(:define_method, :default_proc) do |&λ|
      instance_variable_set(:@λ, λ)
    end
  end
  def [](name)
    super || @λ && @λ.() # or more sophisticated checks
  end
end

Foo = Struct.new(:bar) do
  prepend StructVivificator
end

foo = Foo.new
foo.default_proc { 42 } # declare a `default_proc` as in Hash

foo[:bar] += 1   # => 43
foo.bar += 1     # => 44

foo.barfoo[:bar]上面通过魔术在后台调用method_missing,所以唯一要覆盖的是Struct#[]方法。

预先添加一个模块使其更健壮、更适合每个实例并且通常更灵活。


上面的代码只是一个例子。要复制Hash#default_proc一个可能的行为(感谢@Stefan 的评论):

module StructVivificator
  def self.prepended(base)
    raise 'Sorry, structs only!' unless base < Struct

    base.singleton_class.prepend(Module.new do
      def new(*args, &λ) # override `new` to accept block
        super(*args).tap { @λ = λ }
      end
    end)
    base.send(:define_method, :default_proc=) { |λ| @λ = λ }
    base.send(:define_method, :default_proc) { |&λ| λ ? @λ = λ : @λ }

    # override accessors (additional advantage: performance/clarity)
    base.members.each do |m|
      base.send(:define_method, m) { self[m] }
      base.send(:define_method, "#{m}=") { |value| self[m] = value }
    end
  end
  def [](name)
    super || default_proc && default_proc.(name) # or more sophisticated checks
  end
end

现在default_proclambda 将收到一个name来决定在这种情况下如何表现。

Foo = Struct.new(:bar, :baz) do
  prepend StructVivificator
end

foo = Foo.new
foo.default_proc = ->(name) { name == :bar ? 42 : 0 }
puts foo.bar          # => 42
puts foo[:bar] += 1   # => 43
puts foo.bar += 1     # => 44
puts foo[:baz] += 1   # => 1
于 2016-12-13T07:14:16.960 回答