dry-initializer icon indicating copy to clipboard operation
dry-initializer copied to clipboard

ordering of option declarations should not prohibit forward reference in default procs

Open ahoward opened this issue 3 years ago • 6 comments

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

ahoward avatar Jun 30 '22 03:06 ahoward

How would it work, though? Implementation-wise I mean.

flash-gordon avatar Jun 30 '22 09:06 flash-gordon

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

ahoward avatar Jun 30 '22 17:06 ahoward

i'll see if i can gin up a PR

ahoward avatar Jun 30 '22 17:06 ahoward

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.

flash-gordon avatar Jul 02 '22 11:07 flash-gordon

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....

ahoward avatar Jul 06 '22 00:07 ahoward

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 }

flash-gordon avatar Jul 06 '22 09:07 flash-gordon