dry-types 1.2.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +405 -225
  3. data/LICENSE +1 -1
  4. data/README.md +14 -12
  5. data/dry-types.gemspec +26 -31
  6. data/lib/dry-types.rb +1 -1
  7. data/lib/dry/types.rb +55 -40
  8. data/lib/dry/types/any.rb +2 -2
  9. data/lib/dry/types/array.rb +2 -2
  10. data/lib/dry/types/array/constructor.rb +1 -1
  11. data/lib/dry/types/array/member.rb +1 -1
  12. data/lib/dry/types/builder.rb +70 -18
  13. data/lib/dry/types/builder_methods.rb +1 -2
  14. data/lib/dry/types/coercions.rb +0 -17
  15. data/lib/dry/types/coercions/json.rb +22 -5
  16. data/lib/dry/types/coercions/params.rb +20 -3
  17. data/lib/dry/types/compiler.rb +10 -10
  18. data/lib/dry/types/constrained.rb +6 -9
  19. data/lib/dry/types/constraints.rb +3 -3
  20. data/lib/dry/types/constructor.rb +40 -6
  21. data/lib/dry/types/constructor/function.rb +48 -32
  22. data/lib/dry/types/constructor/wrapper.rb +94 -0
  23. data/lib/dry/types/container.rb +1 -1
  24. data/lib/dry/types/core.rb +15 -13
  25. data/lib/dry/types/decorator.rb +2 -9
  26. data/lib/dry/types/default.rb +14 -1
  27. data/lib/dry/types/enum.rb +4 -3
  28. data/lib/dry/types/errors.rb +1 -1
  29. data/lib/dry/types/extensions.rb +2 -2
  30. data/lib/dry/types/extensions/maybe.rb +18 -17
  31. data/lib/dry/types/extensions/monads.rb +1 -1
  32. data/lib/dry/types/fn_container.rb +1 -1
  33. data/lib/dry/types/hash.rb +9 -15
  34. data/lib/dry/types/hash/constructor.rb +1 -1
  35. data/lib/dry/types/inflector.rb +1 -1
  36. data/lib/dry/types/json.rb +15 -15
  37. data/lib/dry/types/lax.rb +3 -6
  38. data/lib/dry/types/map.rb +2 -2
  39. data/lib/dry/types/meta.rb +3 -3
  40. data/lib/dry/types/module.rb +6 -6
  41. data/lib/dry/types/nominal.rb +11 -11
  42. data/lib/dry/types/params.rb +31 -28
  43. data/lib/dry/types/predicate_inferrer.rb +52 -11
  44. data/lib/dry/types/predicate_registry.rb +1 -1
  45. data/lib/dry/types/primitive_inferrer.rb +1 -1
  46. data/lib/dry/types/printer.rb +25 -25
  47. data/lib/dry/types/result.rb +3 -3
  48. data/lib/dry/types/schema.rb +26 -13
  49. data/lib/dry/types/schema/key.rb +15 -6
  50. data/lib/dry/types/spec/types.rb +65 -42
  51. data/lib/dry/types/sum.rb +6 -5
  52. data/lib/dry/types/type.rb +1 -1
  53. data/lib/dry/types/version.rb +1 -1
  54. metadata +27 -78
  55. data/.codeclimate.yml +0 -12
  56. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  57. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
  58. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  59. data/.github/workflows/custom_ci.yml +0 -76
  60. data/.github/workflows/docsite.yml +0 -34
  61. data/.github/workflows/sync_configs.yml +0 -34
  62. data/.gitignore +0 -11
  63. data/.rspec +0 -4
  64. data/.rubocop.yml +0 -92
  65. data/.yardopts +0 -9
  66. data/CODE_OF_CONDUCT.md +0 -13
  67. data/CONTRIBUTING.md +0 -29
  68. data/Gemfile +0 -34
  69. data/Rakefile +0 -22
  70. data/benchmarks/hash_schemas.rb +0 -55
  71. data/benchmarks/lax_schema.rb +0 -15
  72. data/benchmarks/profile_invalid_input.rb +0 -15
  73. data/benchmarks/profile_lax_schema_valid.rb +0 -16
  74. data/benchmarks/profile_valid_input.rb +0 -15
  75. data/benchmarks/schema_valid_vs_invalid.rb +0 -21
  76. data/benchmarks/setup.rb +0 -17
  77. data/docsite/source/array-with-member.html.md +0 -13
  78. data/docsite/source/built-in-types.html.md +0 -116
  79. data/docsite/source/constraints.html.md +0 -31
  80. data/docsite/source/custom-types.html.md +0 -93
  81. data/docsite/source/default-values.html.md +0 -91
  82. data/docsite/source/enum.html.md +0 -69
  83. data/docsite/source/extensions.html.md +0 -15
  84. data/docsite/source/extensions/maybe.html.md +0 -57
  85. data/docsite/source/extensions/monads.html.md +0 -61
  86. data/docsite/source/getting-started.html.md +0 -57
  87. data/docsite/source/hash-schemas.html.md +0 -169
  88. data/docsite/source/index.html.md +0 -156
  89. data/docsite/source/map.html.md +0 -17
  90. data/docsite/source/optional-values.html.md +0 -35
  91. data/docsite/source/sum.html.md +0 -21
@@ -1,69 +0,0 @@
1
- ---
2
- title: Enum
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- In many cases you may want to define an enum. For example, in a blog application a post may have a finite list of statuses. Apart from accessing the current status value, it is useful to have all possible values accessible too. Furthermore, an enum can be a map from, e.g., strings to integers. This is useful for mapping externally-provided integer values to human-readable strings without explicit conversions, see examples.
8
-
9
- ``` ruby
10
- require 'dry-types'
11
- require 'dry-struct'
12
-
13
- module Types
14
- include Dry.Types()
15
- end
16
-
17
- class Post < Dry::Struct
18
- Statuses = Types::String.enum('draft', 'published', 'archived')
19
-
20
- attribute :title, Types::String
21
- attribute :body, Types::String
22
- attribute :status, Statuses
23
- end
24
-
25
- # enum values are frozen, let's be paranoid, doesn't hurt and have potential to
26
- # eliminate silly bugs
27
- Post::Statuses.values.frozen? # => true
28
- Post::Statuses.values.all?(&:frozen?) # => true
29
-
30
- Post::Statuses['draft'] # => "draft"
31
-
32
- # it'll raise if something silly was passed in
33
- Post::Statuses['something silly']
34
- # => Dry::Types::ConstraintError: "something silly" violates constraints
35
-
36
- # nil is considered as something silly too
37
- Post::Statuses[nil]
38
- # => Dry::Types::ConstraintError: nil violates constraints
39
- ```
40
-
41
- Note that if you want to define an enum type with a default, you must call `.default` *before* calling `.enum`, not the other way around:
42
-
43
- ```ruby
44
- # this is the correct usage:
45
- Dry::Types::String.default('red').enum('blue', 'green', 'red')
46
-
47
- # this will raise an error:
48
- Dry::Types::String.enum('blue', 'green', 'red').default('red')
49
- ```
50
-
51
- ### Mappings
52
-
53
- A classic example is mapping integers coming from somewhere (API/database/etc) to something more understandable:
54
-
55
- ```ruby
56
- class Cell < Dry::Struct
57
- attribute :state, Types::String.enum('locked' => 0, 'open' => 1)
58
- end
59
-
60
-
61
- Cell.new(state: 'locked')
62
- # => #<Cell state="locked">
63
-
64
- # Integers are accepted too
65
- Cell.new(state: 0)
66
- # => #<Cell state="locked">
67
- Cell.new(state: 1)
68
- # => #<Cell state="open">
69
- ```
@@ -1,15 +0,0 @@
1
- ---
2
- title: Extensions
3
- layout: gem-single
4
- name: dry-types
5
- sections:
6
- - maybe
7
- - monads
8
- ---
9
-
10
- `dry-types` can be extended with extension. Those extensions are loaded with `Dry::Types.load_extensions`.
11
-
12
- Available extensions:
13
-
14
- - [Maybe](docs::extensions/maybe)
15
- - [Monads](docs::extensions/monads)
@@ -1,57 +0,0 @@
1
- ---
2
- title: Maybe
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- The [dry-monads gem](/gems/dry-monads/) provides approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object. This allows you to pass your type to a `Maybe(x)` block that only executes if `x` returns `Some` or `None`.
8
-
9
- > NOTE: Requires the [dry-monads gem](/gems/dry-monads/) to be loaded.
10
- 1. Load the `:maybe` extension in your application.
11
-
12
- ```ruby
13
- require 'dry-types'
14
-
15
- Dry::Types.load_extensions(:maybe)
16
- module Types
17
- include Dry.Types()
18
- end
19
- ```
20
-
21
- 2. Append `.maybe` to a _Type_ to return a _Monad_ object
22
-
23
- ```ruby
24
- x = Types::Maybe::Strict::Integer[nil]
25
- Maybe(x) { puts(x) }
26
- x = Types::Maybe::Coercible::String[nil]
27
- Maybe(x) { puts(x) }
28
- x = Types::Maybe::Strict::Integer[123]
29
- Maybe(x) { puts(x) }
30
- x = Types::Maybe::Strict::String[123]
31
- Maybe(x) { puts(x) }
32
- ```
33
-
34
- ```ruby
35
- Types::Maybe::Strict::Integer[nil] # None
36
- Types::Maybe::Strict::Integer[123] # Some(123)
37
- Types::Maybe::Coercible::Float[nil] # None
38
- Types::Maybe::Coercible::Float['12.3'] # Some(12.3)
39
- # 'Maybe' types can also accessed by calling '.maybe' on a regular type:
40
- Types::Strict::Integer.maybe # equivalent to Types::Maybe::Strict::Integer
41
- ```
42
-
43
- You can define your own optional types:
44
-
45
- ``` ruby
46
- maybe_string = Types::Strict::String.maybe
47
- maybe_string[nil]
48
- # => None
49
- maybe_string[nil].fmap(&:upcase)
50
- # => None
51
- maybe_string['something']
52
- # => Some('something')
53
- maybe_string['something'].fmap(&:upcase)
54
- # => Some('SOMETHING')
55
- maybe_string['something'].fmap(&:upcase).value_or('NOTHING')
56
- # => "SOMETHING"
57
- ```
@@ -1,61 +0,0 @@
1
- ---
2
- title: Monads
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- The monads extension makes `Dry::Types::Result` objects compatible with `dry-monads`.
8
-
9
- To enable the extension:
10
-
11
- ```ruby
12
- require 'dry/types'
13
- Dry::Types.load_extensions(:monads)
14
- ```
15
-
16
- After loading the extension, you can leverage monad API:
17
-
18
- ```ruby
19
- Types = Dry.Types()
20
-
21
- result = Types::String.try('Jane')
22
- result.class #=> Dry::Types::Result::Success
23
- monad = result.to_monad
24
- monad.class #=> Dry::Monads::Result::Success
25
- monad.value! # => 'Jane'
26
- result = Types::String.try(nil)
27
- result.class #=> Dry::Types::Result::Failure
28
- monad = result.to_monad
29
- monad.class #=> Dry::Monads::Result::Failure
30
- monad.failure # => [#<Dry::Types::ConstraintError>, nil]
31
- Types::String.try(nil)
32
- .to_monad
33
- .fmap { |result| puts "passed: #{result.inspect}" }
34
- .or { |error, input| puts "input '#{input.inspect}' failed with error: #{error.to_s}" }
35
- ```
36
-
37
- This can be useful when used with `dry-monads` and the [`do` notation](/gems/dry-monads/1.0/do-notation/):
38
-
39
- ```ruby
40
- require 'dry/types'
41
- Types = Dry.Types()
42
- Dry::Types.load_extensions(:monads)
43
-
44
- class AddTen
45
- include Dry::Monads[:result, :do]
46
-
47
- def call(input)
48
- integer = yield Types::Coercible::Integer.try(input)
49
-
50
- Success(integer + 10)
51
- end
52
- end
53
-
54
- add_ten = AddTen.new
55
-
56
- add_ten.call(10)
57
- # => Success(20)
58
-
59
- add_ten.call('integer')
60
- # => Failure([#<Dry::Types::CoercionError: invalid value for Integer(): "integer">, "integer"])
61
- ```
@@ -1,57 +0,0 @@
1
- ---
2
- title: Getting Started
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- ### Using `Dry::Types` in Your Application
8
-
9
- 1. Make `Dry::Types` available to the application by creating a namespace that includes `Dry::Types`:
10
-
11
- ```ruby
12
- module Types
13
- include Dry.Types()
14
- end
15
- ```
16
-
17
- 2. Reload the environment, & type `Types::Coercible::String` in the ruby console to confirm it worked:
18
-
19
- ``` ruby
20
- Types::Coercible::String
21
- # => #<Dry::Types::Constructor type=#<Dry::Types::Definition primitive=String options={}>>
22
- ```
23
-
24
- ### Creating Your First Type
25
-
26
- 1. Define a struct's types by passing the name & type to the `attribute` method:
27
-
28
- ```ruby
29
- class User < Dry::Struct
30
- attribute :name, Types::String
31
- end
32
- ```
33
-
34
- 2. Define [Custom Types](docs::custom-types) in the `Types` module, then pass the name & type to `attribute`:
35
-
36
- ```ruby
37
- module Types
38
- include Dry.Types()
39
-
40
- Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
41
- Age = Integer.constrained(gt: 18)
42
- end
43
- class User < Dry::Struct
44
- attribute :name, Types::String
45
- attribute :email, Types::Email
46
- attribute :age, Types::Age
47
- end
48
- ```
49
-
50
- 3. Use a `Dry::Struct` as a type:
51
-
52
- ```ruby
53
- class Message < Dry::Struct
54
- attribute :body, Types::String
55
- attribute :to, User
56
- end
57
- ```
@@ -1,169 +0,0 @@
1
- ---
2
- title: Hash Schemas
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- It is possible to define a type for a hash with a known set of keys and corresponding value types. Let's say you want to describe a hash containing the name and the age of a user:
8
-
9
- ```ruby
10
- # using simple kernel coercions
11
- user_hash = Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
12
-
13
- user_hash[name: 'Jane', age: '21']
14
- # => { name: 'Jane', age: 21 }
15
- # :name left untouched and :age was coerced to Integer
16
- ```
17
-
18
- If a value doesn't conform to the type, an error is raised:
19
-
20
- ```ruby
21
- user_hash[name: :Jane, age: '21']
22
- # => Dry::Types::SchemaError: :Jane (Symbol) has invalid type
23
- # for :name violates constraints (type?(String, :Jane) failed)
24
- ```
25
-
26
- All keys are required by default:
27
-
28
- ```ruby
29
- user_hash[name: 'Jane']
30
- # => Dry::Types::MissingKeyError: :age is missing in Hash input
31
- ```
32
-
33
- Extra keys are omitted by default:
34
-
35
- ```ruby
36
- user_hash[name: 'Jane', age: '21', city: 'London']
37
- # => { name: 'Jane', age: 21 }
38
- ```
39
-
40
- ### Default values
41
-
42
- Default types are **only** evaluated if the corresponding key is missing in the input:
43
-
44
- ```ruby
45
- user_hash = Types::Hash.schema(
46
- name: Types::String,
47
- age: Types::Integer.default(18)
48
- )
49
- user_hash[name: 'Jane']
50
- # => { name: 'Jane', age: 18 }
51
-
52
- # nil violates the constraint
53
- user_hash[name: 'Jane', age: nil]
54
- # => Dry::Types::SchemaError: nil (NilClass) has invalid type
55
- # for :age violates constraints (type?(Integer, nil) failed)
56
- ```
57
-
58
- In order to evaluate default types on `nil`, wrap your type with a constructor and map `nil` to `Dry::Types::Undefined`:
59
-
60
- ```ruby
61
- user_hash = Types::Hash.schema(
62
- name: Types::String,
63
- age: Types::Integer.
64
- default(18).
65
- constructor { |value|
66
- value.nil? ? Dry::Types::Undefined : value
67
- }
68
- )
69
-
70
- user_hash[name: 'Jane', age: nil]
71
- # => { name: 'Jane', age: 18 }
72
- ```
73
-
74
- The process of converting types to constructors like that can be automated, see "Type transformations" below.
75
-
76
- ### Optional keys
77
-
78
- By default, all keys are required to present in the input. You can mark a key as optional by adding `?` to its name:
79
-
80
- ```ruby
81
- user_hash = Types::Hash.schema(name: Types::String, age?: Types::Integer)
82
-
83
- user_hash[name: 'Jane']
84
- # => { name: 'Jane' }
85
- ```
86
-
87
- ### Extra keys
88
-
89
- All keys not declared in the schema are silently ignored. This behavior can be changed by calling `.strict` on the schema:
90
-
91
- ```ruby
92
- user_hash = Types::Hash.schema(name: Types::String).strict
93
- user_hash[name: 'Jane', age: 21]
94
- # => Dry::Types::UnknownKeysError: unexpected keys [:age] in Hash input
95
- ```
96
-
97
- ### Transforming input keys
98
-
99
- Keys are supposed to be symbols but you can attach a key tranformation to a schema, e.g. for converting strings into symbols:
100
-
101
- ```ruby
102
- user_hash = Types::Hash.schema(name: Types::String).with_key_transform(&:to_sym)
103
- user_hash['name' => 'Jane']
104
-
105
- # => { name: 'Jane' }
106
- ```
107
-
108
- ### Inheritance
109
-
110
- Hash schemas can be inherited in a sense you can define a new schema based on an existing one. Declared keys will be merged, key and type transformations will be preserved. The `strict` option is also passed to the new schema if present.
111
-
112
- ```ruby
113
- # Building an empty base schema
114
- StrictSymbolizingHash = Types::Hash.schema({}).strict.with_key_transform(&:to_sym)
115
-
116
- user_hash = StrictSymbolizingHash.schema(
117
- name: Types::String
118
- )
119
-
120
- user_hash['name' => 'Jane']
121
- # => { name: 'Jane' }
122
-
123
- user_hash['name' => 'Jane', 'city' => 'London']
124
- # => Dry::Types::UnknownKeysError: unexpected keys [:city] in Hash input
125
- ```
126
-
127
- ### Transforming types
128
-
129
- A schema can transform types with a block. For example, the following code makes all keys optional:
130
-
131
- ```ruby
132
- user_hash = Types::Hash.with_type_transform { |type| type.required(false) }.schema(
133
- name: Types::String,
134
- age: Types::Integer
135
- )
136
-
137
- user_hash[name: 'Jane']
138
- # => { name: 'Jane' }
139
- user_hash[{}]
140
- # => {}
141
- ```
142
-
143
- Type transformations work perfectly with inheritance, you don't have to define same rules more than once:
144
-
145
- ```ruby
146
- SymbolizeAndOptionalSchema = Types::Hash
147
- .schema({})
148
- .with_key_transform(&:to_sym)
149
- .with_type_transform { |type| type.required(false) }
150
-
151
- user_hash = SymbolizeAndOptionalSchema.schema(
152
- name: Types::String,
153
- age: Types::Integer
154
- )
155
-
156
- user_hash['name' => 'Jane']
157
- ```
158
-
159
- You can check key name by calling `.name` on the type argument:
160
-
161
- ```ruby
162
- Types::Hash.with_type_transform do |key|
163
- if key.name.to_s.end_with?('_at')
164
- key.constructor { |v| Time.iso8601(v) }
165
- else
166
- key
167
- end
168
- end
169
- ```
@@ -1,156 +0,0 @@
1
- ---
2
- title: Introduction
3
- layout: gem-single
4
- type: gem
5
- name: dry-types
6
- sections:
7
- - getting-started
8
- - built-in-types
9
- - optional-values
10
- - default-values
11
- - sum
12
- - constraints
13
- - hash-schemas
14
- - array-with-member
15
- - enum
16
- - map
17
- - custom-types
18
- - extensions
19
- ---
20
-
21
- `dry-types` is a simple and extendable type system for Ruby; useful for value coercions, applying constraints, defining complex structs or value objects and more. It was created as a successor to [Virtus](https://github.com/solnic/virtus).
22
-
23
- ### Example usage
24
-
25
- ```ruby
26
- require 'dry-types'
27
- require 'dry-struct'
28
-
29
- module Types
30
- include Dry.Types()
31
- end
32
-
33
- User = Dry.Struct(name: Types::String, age: Types::Integer)
34
-
35
- User.new(name: 'Bob', age: 35)
36
- # => #<User name="Bob" age=35>
37
- ```
38
-
39
- See [Built-in Types](docs::built-in-types/) for a full list of available types.
40
-
41
- By themselves, the basic type definitions like `Types::String` and `Types::Integer` don't do anything except provide documentation about which type an attribute is expected to have. However, there are many more advanced possibilities:
42
-
43
- - `Strict` types will raise an error if passed an attribute of the wrong type:
44
-
45
- ```ruby
46
- class User < Dry::Struct
47
- attribute :name, Types::Strict::String
48
- attribute :age, Types::Strict::Integer
49
- end
50
-
51
- User.new(name: 'Bob', age: '18')
52
- # => Dry::Struct::Error: [User.new] "18" (String) has invalid type for :age
53
- ```
54
-
55
- - `Coercible` types will attempt to convert an attribute to the correct class
56
- using Ruby's built-in coercion methods:
57
-
58
- ```ruby
59
- class User < Dry::Struct
60
- attribute :name, Types::Coercible::String
61
- attribute :age, Types::Coercible::Integer
62
- end
63
-
64
- User.new(name: 'Bob', age: '18')
65
- # => #<User name="Bob" age=18>
66
- User.new(name: 'Bob', age: 'not coercible')
67
- # => ArgumentError: invalid value for Integer(): "not coercible"
68
- ```
69
-
70
- - Use `.optional` to denote that an attribute can be `nil` (see [Optional Values](docs::optional-values)):
71
-
72
- ```ruby
73
- class User < Dry::Struct
74
- attribute :name, Types::String
75
- attribute :age, Types::Integer.optional
76
- end
77
-
78
- User.new(name: 'Bob', age: nil)
79
- # => #<User name="Bob" age=nil>
80
- # name is not optional:
81
- User.new(name: nil, age: 18)
82
- # => Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
83
- # keys must still be present:
84
- User.new(name: 'Bob')
85
- # => Dry::Struct::Error: [User.new] :age is missing in Hash input
86
- ```
87
-
88
- - Add custom constraints (see [Constraints](docs::constraints.html)):
89
-
90
- ```ruby
91
- class User < Dry::Struct
92
- attribute :name, Types::Strict::String
93
- attribute :age, Types::Strict::Integer.constrained(gteq: 18)
94
- end
95
-
96
- User.new(name: 'Bob', age: 17)
97
- # => Dry::Struct::Error: [User.new] 17 (Fixnum) has invalid type for :age
98
- ```
99
-
100
- - Add custom metadata to a type:
101
-
102
- ```ruby
103
- class User < Dry::Struct
104
- attribute :name, Types::String
105
- attribute :age, Types::Integer.meta(info: 'extra info about age')
106
- end
107
- ```
108
-
109
- - Pass values directly to `Dry::Types` without creating an object using `[]`:
110
-
111
- ```ruby
112
- Types::Strict::String["foo"]
113
- # => "foo"
114
- Types::Strict::String["10000"]
115
- # => "10000"
116
- Types::Coercible::String[10000]
117
- # => "10000"
118
- Types::Strict::String[10000]
119
- # Dry::Types::ConstraintError: 1000 violates constraints
120
- ```
121
-
122
- ### Features
123
-
124
- * Support for [constrained types](docs::constraints)
125
- * Support for [optional values](docs::optional-values)
126
- * Support for [default values](docs::default-values)
127
- * Support for [sum types](docs::sum)
128
- * Support for [enums](docs::enum)
129
- * Support for [hash type with type schemas](docs::hash-schemas)
130
- * Support for [array type with members](docs::array-with-member)
131
- * Support for arbitrary meta information
132
- * Support for typed struct objects via [dry-struct](/gems/dry-struct)
133
- * Types are [categorized](docs::built-in-types), which is especially important for optimized and dedicated coercion logic
134
- * Types are composable and reusable objects
135
- * No const-missing magic and complicated const lookups
136
- * Roughly 6-10 x faster than Virtus
137
-
138
- ### Use cases
139
-
140
- `dry-types` is suitable for many use-cases, for example:
141
-
142
- * Value coercions
143
- * Processing arrays
144
- * Processing hashes with explicit schemas
145
- * Defining various domain-specific information shared between multiple parts of your application
146
- * Annotating objects
147
-
148
- ### Other gems using dry-types
149
-
150
- `dry-types` is often used as a low-level abstraction. The following gems use it already:
151
-
152
- * [dry-struct](/gems/dry-struct)
153
- * [dry-initializer](/gems/dry-initializer)
154
- * [Hanami](http://hanamirb.org)
155
- * [rom-rb](http://rom-rb.org)
156
- * [Trailblazer](http://trailblazer.to)