I like using dry-initializer
in the context of function objects mainly because of the dry-types
integration it provides. As the initializer
method is the main entry point for the data to be handled, it is very convenient in order to adding a DSL layer of type safety.
However, sometimes the state to be consumed by the “function” within the object (usually from the #call
method) is not the same that the data from which the object is initialized. This is because you might want your consumer to provide some data but process it in some way before being consumed. A very simple example:
attr_reader :to_be_consumed
def initializer(to_initialize:)
@to_be_consumed = to_initialize + 1
end
I know in dry-initializer you can do something like:
extend Dry::Initializer
option :to_initialize
option :to_be_consumed, default: proc { to_initialize + 1 }
But this is not semantically the same, as we are getting an object which require two options to be initialized. I also know I can easily define my own initialize
and call super
from there, and it is a quite satisfactory solution. However, due that I feel it is quite a common scenario, I think It could be nice to support it with the DSL. For example:
attr_reader :to_consume
option :to_initialize
process do |to_initialize:|
@to_consume = to_initialize + 1
end
What do you think?
I would say coercer solves your problem more directly:
extend Dry::Initializer
option :to_be_consumed, ->(v) { v.to_i + 1 }
alternatively you can use the :type
option
extend Dry::Initializer
option :to_be_consumed, type: ->(v) { v.to_i + 1 }
Hey @nepalez, thanks for your answer.
I see my example was too contrived. As you say, coercer solves a lot of scenarios, but sometimes you want to initialize something from two options, or just initialize something without the need of any of the arguments. Another contrived example:
def initialize(a, b)
@c = C.new(a, b)
@d = D.new
end
@waiting-for-dev yes, you’re right that not any process of initialization is covered by the gem.
The most obvious example is the gem doesn’t allow processing a block of code like the following initializer does:
def initialize(&block)
@block = block
end
But I don’t think we ever should try doing this. In my opinion, overloading the initializer with super
method is much cleaner than adding a special DSL method for post-processing. Personally I treat DSL as a necessary evil, but it must be necessary.
I share with you your reticence about adding DSL to every need that we, library consumers, can experience. In my use case, I’m not using dry-initializer
out of necessity but just because I feel its convenience to apply type checking to initialization data pays off. I could do the same without it, but my code would have the same pattern applied one time and another. Then, now I find myself repeating the pattern of calling super
to do post-processing and, anyway, I never find clean having to call super
. Surely, it is just that we have different views here Thanks anyway for your feedback and for your great work on this