如何实现 Ruby 结构的自动生成?
How to implement autovivification for Ruby structs?
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.bar
和 foo[:bar]
形式都有效的方法?
我会采用以下方法:
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.bar
上面通过 method_missing
魔法调用 foo[:bar]
,所以唯一要覆盖的是 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_proc
lambda 将收到一个 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
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.bar
和 foo[:bar]
形式都有效的方法?
我会采用以下方法:
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.bar
上面通过 method_missing
魔法调用 foo[:bar]
,所以唯一要覆盖的是 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_proc
lambda 将收到一个 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