What is the purpose of providing a type to the `Constructor` function when block is given?

Hey, consider following snippet with types declared with Constructor function:

without_block = Types.Constructor(String)

without_block[123]    # Dry::Types::CoercionError (no implicit conversion of Integer into String)
without_block['abc']  # 'abc'
without_block['123']  # '123'

with_block = Types.Constructor(String) { |value| Integer(value) }

with_block[123]    # 123
with_block['abc']  # Dry::Types::CoercionError (invalid value for Integer(): "abc")
with_block['123']  # 123

Types.Constructor { |value| Integer(value) } # ArgumentError (wrong number of arguments (given 0, expected 1..2))
  • Type with_block returns Integer, which is kind of obvious because provided block returns Integer.
  • Type with_block accepts both 123 and '123' because it’s being passed straight to the block.
  • You can’t use Constructor method only with block, without a type.

Given that, my question is:
What is the purpose of providing a type to the Constructor function when block is given?

In a nutshell, because dry-types is about types, it can’t infer the type from a lambda so you need to give it one. If you don’t care about types you can use Types::Any.constructor { ... }. Types are used explicitly or implicitly in different contexts, rom and rom-sql is a perfect example of this. Using Types::Any leaves libraries no way to know what your data is, it’s completely opaque. Another example is generating swagger schemas, we don’t have a working open source solution for this but it’s entirely possible to build one. Types::Any will be represented as something meaningless in this case.