Я хотел бы пойти со следующим подходом:
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
выше называет foo[: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_proc
лямбда получит 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
Ваши примеры на самом деле не эквивалентны. Вызывается обработчик по умолчанию hash, потому что 'hash' не имеет ключа': foo'. У экземпляра struct 'foo' с другой стороны есть член': bar' с начальным значением 'nil'. – Stefan
@Stefan Спасибо, это очень верно. И на самом деле это ключ к тому, что мне нужно делать: мне не нужны ленивые аспекты автовивитации, просто нужно, чтобы некоторые поля были настроены так, чтобы они были не ноль. И получается, что настройка метода 'initialize' для настройки начальных значений полей сделает именно это. Еще раз спасибо. :-) –