dry-initializer
dry-initializer copied to clipboard
ordering of option declarations should not prohibit forward reference in default procs
Describe the bug
the :default keyword supports an "instance_exec" type of evaluation whereby default: -> { foo + bar }' reference the object in questions' fooand barmethods. however, when eitherfooorbarare, themselves, dry-initialized attributes, the default value will resolve tonil` due to being referenced prior to 'dry initialization'
To Reproduce
require 'dry-initializer'
class A
extend Dry::Initializer
option :foo, default: -> { bar }
option :bar, default: -> { 42 }
end.new
class B
extend Dry::Initializer
option :bar, default: -> { 42 }
option :foo, default: -> { bar }
end.new
pp A.new #=> #<A:0x0000000103009958 @bar=42, @foo=nil>
pp B.new #=> #<B:0x000000010302aea0 @bar=42, @foo=42>
Expected behavior
@bar == @foo == 42
My environment
n/a
How would it work, though? Implementation-wise I mean.
essentially, the reader must be read consistent. specifically, the first invocation needs to memo-ize the default proc, vs simply looking at the list of options. eg.
ivar = "@#{ name }"
defined_method(name) do
initialize_default_value_for(name) unless instance_variable_defined?(ivar)
instance_variable_get(ivar)
end
i'll see if i can gin up a PR
But this would break the assumption an object is fully initialized after .new call. Normally, I don't mutate objects meaning I call .freeze after initialization. Your suggestion will break in such a use case.
not necessarily. one would simple need to hit all readers to initialize the values before freezing. freezing is nice but it also means that objects are expensive, as memoization is good for speed. regardless, it's a bug-ette in dry.rb now, since a specified default is not applied, depending on ordering. if this is by design so be it but i'm guessing it's a side-effect of other design goals. i think all goals can be achieve. the code is fairly abstract but i'm working on something now....
Freezing is not about the cost actually. It's a technique of separating boot time from runtime. An application stack and its constituents are meant to be immutable, hence frozen.
Anyway, handling such cases is somewhat problematic:
option :foo, default: -> { bar }
option :bar, default: -> { foo }