Why interfaces of dry-struct and dry-initializer are different?

Is there a reason why these two libraries have a very similar interface but not identical?

dry-struct:

class User < Dry::Struct
  attribute :name, Types::String.optional
  attribute :age, Types::Coercible::Integer
end

dry-initializer:

class User
  extend Dry::Initializer
  
  option :name, proc(&:to_s), optional: true
  option :age, Types::Coercible::Integer
end

My understanding is that the main feature of dry-struct is that it is deeply frozen and can use == operation. Is it possible to express same semantics of dry-struct with a syntax as for dry-initializer? In a project that already has dry-initializer introducing another syntax for basically the same thing is questionable, if equality is not immediately needed. At the same time dry-struct has useful semantics. Would be good to avoid having to make this trade-off.

They are different because they are built for entirely different use-cases.

dry-initializer is a dependency-injection syntax. It is for defining dependency arguments for an object that performs some function with them.

dry-struct is a data object syntax. It defines a compound type.

Put another way, dry-initializer is for verbs, dry-struct is for nouns. If you are injecting plain data via dry-initializer, personally I think that is a misuse. You should be injecting behavior. Data is passed in as arguments.

Thanks for your thoughts! I get the idea that they are built for different purposes and that there’s a certain way the things “should” be done. At the same time I would like to explore the technical implications of trying to bridge the two interfaces closer, for example, what if dry-struct used option instead of attribute:

class User < Dry::Struct
  option :name, Types::String.optional
  option :age, Types::Coercible::Integer
end

The one downside I see is that now it could be impossible to to use dry-struct and dry-initializer together in the same class, but it shouldn’t be an issue in the real world.

Put another way, dry-initializer is for verbs, dry-struct is for nouns.

I also have this kind of a mental concept around the library use. At the same time they do have clear intersections and right now I really use dry-initializer in a place where dry-struct would be a semantically more correct option.

My question would be, are there any features that are definitely impossible to express in the same language for both use cases of dry-struct and dry-initializer?

This will be properly announced soon but we’re actually going to deprecate dry-initializer and port it to dry-core and eventually simplify it too. The confusion between dry-initializer and dry-struct is something we want to avoid. dry-rb promotes data-centric approach to programming and dry-initializer exposed as a top-level gem is working against it in a way. For dependency injection we have dry-auto_inject and dry-system.

Thank you for the insight! It would be interesting to take a look at what you come up with, will be looking forward to it!

Merging into dry-core makes a lot of sense, but I hope the general idea doesn’t go away.

I found it very useful to start smaller by defining my own injections without a DI system. If someone is skeptical about the general utility of DI, having to go all the way to dry-auto_inject and dry-system may turn people away.

The idea will definitely stay. However, as @solnic mentioned, we promote a different approach for building applications.