kind 3.0.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -0
  3. data/.travis.sh +42 -12
  4. data/.travis.yml +7 -5
  5. data/CHANGELOG.md +1309 -0
  6. data/Gemfile +22 -7
  7. data/README.md +990 -490
  8. data/kind.gemspec +1 -1
  9. data/lib/kind.rb +29 -292
  10. data/lib/kind/active_model/validation.rb +3 -4
  11. data/lib/kind/core.rb +15 -0
  12. data/lib/kind/core/dig.rb +40 -0
  13. data/lib/kind/core/empty.rb +13 -0
  14. data/lib/kind/core/empty/constant.rb +7 -0
  15. data/lib/kind/{error.rb → core/error.rb} +2 -6
  16. data/lib/kind/core/maybe.rb +42 -0
  17. data/lib/kind/core/maybe/none.rb +57 -0
  18. data/lib/kind/core/maybe/result.rb +51 -0
  19. data/lib/kind/core/maybe/some.rb +90 -0
  20. data/lib/kind/core/maybe/typed.rb +29 -0
  21. data/lib/kind/core/maybe/wrappable.rb +33 -0
  22. data/lib/kind/core/presence.rb +33 -0
  23. data/lib/kind/core/try.rb +34 -0
  24. data/lib/kind/core/type_checker.rb +87 -0
  25. data/lib/kind/core/type_checkers.rb +30 -0
  26. data/lib/kind/core/type_checkers/custom/boolean.rb +19 -0
  27. data/lib/kind/core/type_checkers/custom/callable.rb +19 -0
  28. data/lib/kind/core/type_checkers/custom/lambda.rb +19 -0
  29. data/lib/kind/core/type_checkers/ruby/array.rb +17 -0
  30. data/lib/kind/core/type_checkers/ruby/class.rb +13 -0
  31. data/lib/kind/core/type_checkers/ruby/comparable.rb +13 -0
  32. data/lib/kind/core/type_checkers/ruby/enumerable.rb +13 -0
  33. data/lib/kind/core/type_checkers/ruby/enumerator.rb +13 -0
  34. data/lib/kind/core/type_checkers/ruby/file.rb +13 -0
  35. data/lib/kind/core/type_checkers/ruby/float.rb +13 -0
  36. data/lib/kind/core/type_checkers/ruby/hash.rb +17 -0
  37. data/lib/kind/core/type_checkers/ruby/integer.rb +13 -0
  38. data/lib/kind/core/type_checkers/ruby/io.rb +13 -0
  39. data/lib/kind/core/type_checkers/ruby/method.rb +13 -0
  40. data/lib/kind/core/type_checkers/ruby/module.rb +17 -0
  41. data/lib/kind/core/type_checkers/ruby/numeric.rb +13 -0
  42. data/lib/kind/core/type_checkers/ruby/proc.rb +13 -0
  43. data/lib/kind/core/type_checkers/ruby/queue.rb +14 -0
  44. data/lib/kind/core/type_checkers/ruby/range.rb +13 -0
  45. data/lib/kind/core/type_checkers/ruby/regexp.rb +13 -0
  46. data/lib/kind/core/type_checkers/ruby/string.rb +17 -0
  47. data/lib/kind/core/type_checkers/ruby/struct.rb +13 -0
  48. data/lib/kind/core/type_checkers/ruby/symbol.rb +13 -0
  49. data/lib/kind/core/type_checkers/ruby/time.rb +13 -0
  50. data/lib/kind/core/type_checkers/stdlib/open_struct.rb +13 -0
  51. data/lib/kind/core/type_checkers/stdlib/set.rb +17 -0
  52. data/lib/kind/{undefined.rb → core/undefined.rb} +4 -2
  53. data/lib/kind/core/utils/kind.rb +61 -0
  54. data/lib/kind/core/utils/undefined.rb +7 -0
  55. data/lib/kind/validator.rb +108 -1
  56. data/lib/kind/version.rb +1 -1
  57. data/test.sh +4 -4
  58. metadata +50 -15
  59. data/lib/kind/active_model/kind_validator.rb +0 -96
  60. data/lib/kind/checker.rb +0 -15
  61. data/lib/kind/checker/factory.rb +0 -35
  62. data/lib/kind/checker/protocol.rb +0 -73
  63. data/lib/kind/empty.rb +0 -21
  64. data/lib/kind/is.rb +0 -19
  65. data/lib/kind/maybe.rb +0 -183
  66. data/lib/kind/of.rb +0 -11
  67. data/lib/kind/types.rb +0 -115
data/Gemfile CHANGED
@@ -12,22 +12,37 @@ activemodel = case activemodel_version
12
12
  when '5.0' then '5.0.7'
13
13
  when '5.1' then '5.1.7'
14
14
  when '5.2' then '5.2.4'
15
- when '6.0' then '6.0.0'
15
+ when '6.0' then '6.0.3.4'
16
+ when '6.1' then '6.1.2'
16
17
  end
17
18
 
19
+ simplecov_version =
20
+ case RUBY_VERSION
21
+ when /\A2.[123]/ then '0.17.1'
22
+ when /\A2.4/ then '~> 0.18.5'
23
+ else '~> 0.19'
24
+ end
25
+
26
+ is_ruby_2_1 = RUBY_VERSION <= '2.2.0'
27
+
28
+ minitest_version =
29
+ if activemodel_version
30
+ activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
31
+ else
32
+ is_ruby_2_1 ? '~> 4.2' : '~> 5.0'
33
+ end
34
+
18
35
  group :test do
19
36
  if activemodel_version
20
37
  gem 'activesupport', activemodel, require: false
21
- gem 'activemodel', activemodel, require: false
22
- gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
23
- else
24
- gem 'minitest', '~> 5.0'
38
+ gem 'activemodel' , activemodel, require: false
25
39
  end
26
40
 
27
- gem 'simplecov', require: false
41
+ gem 'minitest' , minitest_version
42
+ gem 'simplecov', simplecov_version, require: false
28
43
  end
29
44
 
30
- gem 'rake', '~> 13.0'
45
+ gem 'rake', is_ruby_2_1 ? '~> 12.3' : '~> 13.0'
31
46
 
32
47
  # Specify your gem's dependencies in type_validator.gemspec
33
48
  gemspec
data/README.md CHANGED
@@ -1,63 +1,134 @@
1
- ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
2
- [![Gem](https://img.shields.io/gem/v/kind.svg?style=flat-square)](https://rubygems.org/gems/kind)
3
- [![Build Status](https://travis-ci.com/serradura/kind.svg?branch=master)](https://travis-ci.com/serradura/kind)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability)](https://codeclimate.com/github/serradura/kind/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/711329decb2806ccac95/test_coverage)](https://codeclimate.com/github/serradura/kind/test_coverage)
1
+ <p align="center">
2
+ <h1 align="center">🤷 kind</h1>
3
+ <p align="center"><i>A simple type system (at runtime) for Ruby - free of dependencies.</i></p>
4
+ <br>
5
+ </p>
6
6
 
7
- # Kind <!-- omit in toc -->
7
+ <p align="center">
8
+ <a href="https://rubygems.org/gems/kind">
9
+ <img alt="Gem" src="https://img.shields.io/gem/v/kind.svg?style=flat-square">
10
+ </a>
8
11
 
9
- A simple type system (at runtime) for Ruby - free of dependencies.
12
+ <img src="https://img.shields.io/badge/ruby%20%3E=%202.1.0,%20%3C%203.1-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
13
+
14
+ <img src="https://img.shields.io/badge/rails%20%3E=%203.2.0,%20%3C%207-rails.svg?colorA=8B0000&colorB=FF0000" alt="Rails">
15
+
16
+ <br />
17
+
18
+ <a href="https://travis-ci.com/serradura/kind">
19
+ <img alt="Build Status" src="https://travis-ci.com/serradura/kind.svg?branch=master">
20
+ </a>
21
+
22
+ <a href="https://codeclimate.com/github/serradura/kind/maintainability">
23
+ <img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability">
24
+ </a>
25
+
26
+ <a href="https://codeclimate.com/github/serradura/kind/test_coverage">
27
+ <img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/711329decb2806ccac95/test_coverage">
28
+ </a>
29
+ </p>
10
30
 
11
31
  **Motivation:**
12
32
 
13
33
  As a creator of Ruby gems, I have a common need that I have to handle in many of my projects: type checking of method arguments.
14
34
 
15
- One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions to do it. e.g: [Kind.of.\<Type\> methods](#verifying-the-kind-of-some-object), [active model validations](#kindvalidator-activemodelvalidations), [maybe monad](#kindmaybe).
35
+ One of the goals of this project is to do simple type checking like `"some string".is_a?(String)`, but, exposing useful abstractions around this. e.g: [Kind.\<Type\> methods](#verifying-the-kind-of-some-object), [active model validations](#kindvalidator-activemodelvalidations), [maybe monad](#kindmaybe).
36
+
37
+ ## Documentation <!-- omit in toc -->
38
+
39
+ Version | Documentation
40
+ ---------- | -------------
41
+ unreleased | https://github.com/serradura/u-case/blob/main/README.md
42
+ 5.1.0 | https://github.com/serradura/u-case/blob/v5.x/README.md
43
+ 4.1.0 | https://github.com/serradura/u-case/blob/v4.x/README.md
44
+ 3.1.0 | https://github.com/serradura/u-case/blob/v3.x/README.md
45
+ 2.3.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
46
+ 1.9.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
16
47
 
17
48
  ## Table of Contents <!-- omit in toc -->
18
- - [Required Ruby version](#required-ruby-version)
49
+ - [Compatibility](#compatibility)
19
50
  - [Installation](#installation)
20
51
  - [Usage](#usage)
21
- - [Kind.of.\<Type\>() - Verifying the kind of some object](#kindoftype---verifying-the-kind-of-some-object)
22
- - [Method aliases to perform a strict validation](#method-aliases-to-perform-a-strict-validation)
23
- - [Kind.of.\<Type\>.or_nil()](#kindoftypeor_nil)
24
- - [Kind.of.\<Type\>.instance?()](#kindoftypeinstance)
25
- - [Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.](#kindistype---verifying-if-some-classmodule-is-the-expected-kind)
26
- - [How to create a new type checker?](#how-to-create-a-new-type-checker)
27
- - [Creating/Verifiyng type checkers dynamically](#creatingverifiyng-type-checkers-dynamically)
28
- - [Registering new (custom) type checker](#registering-new-custom-type-checker)
29
- - [What happens if a custom type checker has a namespace?](#what-happens-if-a-custom-type-checker-has-a-namespace)
30
- - [Type checkers](#type-checkers)
31
- - [Classes' type checkers](#classes-type-checkers)
32
- - [Modules' type checkers](#modules-type-checkers)
33
- - [Specials' type checkers](#specials-type-checkers)
34
- - [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
35
- - [Usage](#usage-1)
36
- - [Defining the default validation strategy](#defining-the-default-validation-strategy)
37
- - [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
52
+ - [Kind.\<Type\>[]](#kindtype)
53
+ - [Kind::\<Type\>.===()](#kindtype-1)
54
+ - [Kind::\<Type\>.value?()](#kindtypevalue)
55
+ - [Kind::\<Type\>.or_nil()](#kindtypeor_nil)
56
+ - [Kind::\<Type\>.or_undefined()](#kindtypeor_undefined)
57
+ - [Kind::\<Type\>.or()](#kindtypeor)
58
+ - [Kind::\<Type\>.value()](#kindtypevalue-1)
59
+ - [Kind::\<Type\>.maybe](#kindtypemaybe)
60
+ - [Kind::\<Type\>?](#kindtype-2)
61
+ - [Kind::{Array,Hash,String,Set}.value_or_empty()](#kindarrayhashstringsetvalue_or_empty)
62
+ - [List of all type checkers](#list-of-all-type-checkers)
63
+ - [Core](#core)
64
+ - [Stdlib](#stdlib)
65
+ - [Custom](#custom)
66
+ - [Creating type checkers](#creating-type-checkers)
67
+ - [Dynamic creation](#dynamic-creation)
68
+ - [Using a class or a module](#using-a-class-or-a-module)
69
+ - [Using an object which responds to ===](#using-an-object-which-responds-to-)
70
+ - [Kind::<Type> module](#kindtype-module)
71
+ - [Utility methods](#utility-methods)
72
+ - [Kind.of_class?()](#kindof_class)
73
+ - [Kind.of_module?()](#kindof_module)
74
+ - [Kind.of_module_or_class()](#kindof_module_or_class)
75
+ - [Kind.respond_to()](#kindrespond_to)
76
+ - [Kind.of()](#kindof)
77
+ - [Kind.of?()](#kindof-1)
78
+ - [Kind.value()](#kindvalue)
79
+ - [Kind.is()](#kindis)
80
+ - [Utility modules](#utility-modules)
81
+ - [Kind::Try](#kindtry)
82
+ - [Kind::Dig](#kinddig)
83
+ - [Kind::Presence](#kindpresence)
38
84
  - [Kind::Undefined](#kindundefined)
39
- - [Kind.of.\<Type\>.or_undefined()](#kindoftypeor_undefined)
40
85
  - [Kind::Maybe](#kindmaybe)
41
86
  - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)
42
87
  - [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)
43
88
  - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)
44
89
  - [Kind::None() and Kind::Some()](#kindnone-and-kindsome)
45
- - [Kind.of.Maybe()](#kindofmaybe)
46
90
  - [Kind::Optional](#kindoptional)
47
91
  - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)
48
- - [Kind.of.\<Type\>.as_optional](#kindoftypeas_optional)
49
92
  - [Kind::Maybe(<Type>)](#kindmaybetype)
93
+ - [Real world examples](#real-world-examples)
94
+ - [Error handling](#error-handling)
95
+ - [Kind::Maybe.wrap {}](#kindmaybewrap-)
96
+ - [Kind::Maybe.map! or Kind::Maybe.then!](#kindmaybemap-or-kindmaybethen)
50
97
  - [Kind::Maybe#try](#kindmaybetry)
51
98
  - [Kind::Maybe#try!](#kindmaybetry-1)
99
+ - [Kind::Maybe#dig](#kindmaybedig)
100
+ - [Kind::Maybe#check](#kindmaybecheck)
101
+ - [Kind::Maybe#presence](#kindmaybepresence)
52
102
  - [Kind::Empty](#kindempty)
103
+ - [Defining Empty as Kind::Empty an alias](#defining-empty-as-kindempty-an-alias)
104
+ - [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)
105
+ - [Usage](#usage-1)
106
+ - [Object#===](#object)
107
+ - [Kind.is](#kindis-1)
108
+ - [Object#instance_of?](#objectinstance_of)
109
+ - [Object#respond_to?](#objectrespond_to)
110
+ - [Array.new.all? { |item| item.kind_of?(Class) }](#arraynewall--item-itemkind_ofclass-)
111
+ - [Array.new.all? { |item| expected_values.include?(item) }](#arraynewall--item-expected_valuesincludeitem-)
112
+ - [Defining the default validation strategy](#defining-the-default-validation-strategy)
113
+ - [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)
53
114
  - [Similar Projects](#similar-projects)
54
115
  - [Development](#development)
55
116
  - [Contributing](#contributing)
56
117
  - [License](#license)
57
118
  - [Code of Conduct](#code-of-conduct)
58
119
 
59
- ## Required Ruby version
60
- > \>= 2.2.0
120
+ ## Compatibility
121
+
122
+ | u-case | branch | ruby | activemodel |
123
+ | -------------- | ------- | -------- | -------------- |
124
+ | unreleased | main | >= 2.1.0 | >= 3.2, <= 6.1 |
125
+ | 5.1.0 | v5.x | >= 2.1.0 | >= 3.2, <= 6.1 |
126
+ | 4.1.0 | v4.x | >= 2.2.0 | >= 3.2, <= 6.1 |
127
+ | 3.1.0 | v3.x | >= 2.2.0 | >= 3.2, <= 6.1 |
128
+ | 2.3.0 | v2.x | >= 2.2.0 | >= 3.2, <= 6.0 |
129
+ | 1.9.0 | v1.x | >= 2.2.0 | >= 3.2, <= 6.0 |
130
+
131
+ > Note: The activemodel is an optional dependency, it is related with the [Kind::Validator](#kindvalidator-activemodelvalidations).
61
132
 
62
133
  ## Installation
63
134
 
@@ -75,7 +146,7 @@ Or install it yourself as:
75
146
 
76
147
  $ gem install kind
77
148
 
78
- [⬆️ Back to Top](#table-of-contents-)
149
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
79
150
 
80
151
  ## Usage
81
152
 
@@ -83,7 +154,7 @@ With this gem you can add some kind of type checking at runtime. e.g:
83
154
 
84
155
  ```ruby
85
156
  def sum(a, b)
86
- Kind.of.Numeric(a) + Kind.of.Numeric(b)
157
+ Kind::Numeric[a] + Kind::Numeric[b]
87
158
  end
88
159
 
89
160
  sum(1, 1) # 2
@@ -91,200 +162,227 @@ sum(1, 1) # 2
91
162
  sum('1', 1) # Kind::Error ("\"1\" expected to be a kind of Numeric")
92
163
  ```
93
164
 
94
- ### Kind.of.\<Type\>() - Verifying the kind of some object
165
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
166
+
167
+ ### Kind.\<Type\>[]
95
168
 
96
- By default, basic verifications are strict. So, when you perform `Kind.of.Hash(value)`, if the given value was a Hash, the value itself will be returned, but if it isn't the right type, an error will be raised.
169
+ By default, basic verifications are strict. So, when you perform `Kind::Hash[value]` the given value will be returned if it was a Hash, but if not, an error will be raised.
97
170
 
98
171
  ```ruby
99
- Kind.of.Hash(nil) # **raise Kind::Error, "nil expected to be a kind of Hash"**
100
- Kind.of.Hash('') # raise Kind::Error, "'' expected to be a kind of Hash"
101
- Kind.of.Hash(a: 1) # {a: 1}
172
+ Kind::Hash[nil] # Kind::Error (nil expected to be a kind of Hash)
173
+ Kind::Hash[''] # Kind::Error ("" expected to be a kind of Hash)
174
+ Kind::Hash[a: 1] # {a: 1}
175
+ ```
102
176
 
103
- # ---
177
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
104
178
 
105
- Kind.of.Boolean(nil) # raise Kind::Error, "nil expected to be a kind of Boolean"
106
- Kind.of.Boolean(true) # true
107
- Kind.of.Boolean(false) # false
108
- ```
179
+ ### Kind::\<Type\>.===()
109
180
 
110
- > **Note:** `Kind.of.<Type>` supports the to_proc protocol.
111
- > And it will perform a strict validation as expected.
181
+ Use this method to verify if the given object has the expected type.
112
182
 
113
183
  ```ruby
114
- collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
115
-
116
- collection.map(&Kind.of.Hash) # Kind::Error ("number 2" expected to be a kind of Hash)
184
+ Kind::Enumerable === {} # true
185
+ Kind::Enumerable === '' # false
117
186
  ```
118
187
 
119
- When the verified value is nil, it is possible to define a default value with the same type to be returned.
188
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
120
189
 
121
- ```ruby
122
- value = nil
190
+ ### Kind::\<Type\>.value?()
123
191
 
124
- Kind.of.Hash(value, or: {}) # {}
192
+ This method works like `.===`, but the difference is what happens when you invoke it without arguments.
125
193
 
126
- # ---
194
+ ```ruby
195
+ # Example of calling `.value?` with an argument:
127
196
 
128
- Kind.of.Boolean(nil, or: true) # true
197
+ Kind::Enumerable.value?({}) # true
198
+ Kind::Enumerable.value?('') # false
129
199
  ```
130
200
 
131
- > **Note:** As an alternative syntax, you can use the `Kind::Of` instead of the `Kind.of` method. e.g: `Kind::Of::Hash('')`
132
-
133
- #### Method aliases to perform a strict validation
201
+ When `.value?` is called without an argument, it will return a lambda which will know how to perform the kind verification.
134
202
 
135
203
  ```ruby
136
- Kind.of.Hash[nil] # raise Kind::Error, "nil expected to be a kind of Hash"
137
- Kind.of.Hash[''] # raise Kind::Error, "'' expected to be a kind of Hash"
138
- Kind.of.Hash[a: 1] # {a: 1}
139
- Kind.of.Hash['', or: {}] # {}
140
-
141
- # or
204
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
142
205
 
143
- Kind.of.Hash.instance(nil) # raise Kind::Error, "nil expected to be a kind of Hash"
144
- Kind.of.Hash.instance('') # raise Kind::Error, "'' expected to be a kind of Hash"
145
- Kind.of.Hash.instance(a: 1) # {a: 1}
146
- Kind.of.Hash.instance('', or: {}) # {}
206
+ collection.select(&Kind::Enumerable.value?) # [{:number=>1}, {:number=>3}, [:number, 5]]
147
207
  ```
148
208
 
149
- ### Kind.of.\<Type\>.or_nil()
209
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
210
+
211
+ ### Kind::\<Type\>.or_nil()
150
212
 
151
213
  But if you don't need a strict type verification, use the `.or_nil` method.
152
214
 
153
215
  ```ruby
154
- Kind.of.Hash.or_nil('') # nil
155
- Kind.of.Hash.or_nil({a: 1}) # {a: 1}
216
+ Kind::Hash.or_nil('') # nil
217
+ Kind::Hash.or_nil({a: 1}) # {a: 1}
218
+ ```
156
219
 
157
- # ---
220
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
221
+
222
+ ### Kind::\<Type\>.or_undefined()
158
223
 
159
- Kind.of.Boolean.or_nil('') # nil
160
- Kind.of.Boolean.or_nil(true) # true
224
+ This method works like `.or_nil`, but it will return a [`Kind::Undefined`](#kindundefined) instead of `nil`.
225
+
226
+ ```ruby
227
+ Kind::Hash.or_undefined('') # Kind::Undefined
228
+ Kind::Hash.or_undefined({a: 1}) # {a: 1}
161
229
  ```
162
230
 
163
- ### Kind.of.\<Type\>.instance?()
231
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
164
232
 
165
- Use the method `.instance?` to verify if the given object has the expected type.
233
+ ### Kind::\<Type\>.or()
166
234
 
167
- ```ruby
168
- Kind.of.Hash.instance?({}) # true
169
- Kind.of.Hash.instance?({}, HashWithIndifferentAccess.new) # true
235
+ This method can return a fallback if the given value isn't an instance of the expected kind.
170
236
 
171
- Kind.of.Hash.instance?('') # false
172
- Kind.of.Hash.instance?({}, '') # false
237
+ ```ruby
238
+ Kind::Hash.or({}, []) # {}
239
+ Kind::Hash.or(nil, []) # nil
240
+ Kind::Hash.or(nil, {a: 1}) # {a: 1}
241
+ ```
173
242
 
174
- # ---
243
+ If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value is wrong.
175
244
 
176
- Kind.of.Boolean.instance?(true) # true
177
- Kind.of.Boolean.instance?(true, false) # true
245
+ ```ruby
246
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
178
247
 
179
- Kind.of.Boolean.instance?(nil) # false
180
- Kind.of.Boolean.instance?(false, true, nil) # false
248
+ collection.map(&Kind::Hash.or({})) # [{:number=>1}, {}, {:number=>3}, {}, {}]
249
+ collection.map(&Kind::Hash.or(nil)) # [{:number=>1}, nil, {:number=>3}, nil, nil]
181
250
  ```
182
251
 
183
- > **Note:** When `.instance?` is called without an argument,
184
- > it will return a lambda which will perform the kind verification.
252
+ An error will be raised if the fallback didn't have the expected kind or if not `nil` / `Kind::Undefined`.
185
253
 
186
254
  ```ruby
187
- collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
255
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
188
256
 
189
- collection
190
- .select(&Kind.of.Hash.instance?) # [{:number=>1}, {:number=>3}]
257
+ collection.map(&Kind::Hash.or(:foo)) # Kind::Error (:foo expected to be a kind of Hash)
191
258
  ```
192
259
 
193
- > **Note:** You can use a different syntax to perform an instance verification.
194
- > To do this, use Kind.of.\<Type\>?()
260
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
261
+
262
+ ### Kind::\<Type\>.value()
195
263
 
264
+ This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
196
265
  ```ruby
197
- Kind.of.Hash?({}) # true
198
- Kind.of.Hash?({}, HashWithIndifferentAccess.new) # true
266
+ Kind::String.value(1, default: '') # ""
199
267
 
200
- Kind.of.Hash?('') # false
201
- Kind.of.Hash?({}, '') # false
268
+ Kind::String.value('1', default: '') # "1"
202
269
 
203
- # ---
270
+ Kind::String.value('1', default: 1) # Kind::Error (1 expected to be a kind of String)
271
+ ```
204
272
 
205
- Kind.of.Boolean?(true) # true
206
- Kind.of.Boolean?(false, true) # true
273
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
207
274
 
208
- Kind.of.Boolean?(nil) # false
209
- Kind.of.Boolean?(false, true, nil) # false
275
+ ### Kind::\<Type\>.maybe
210
276
 
211
- # ---
277
+ This method exposes a [typed `Kind::Maybe`](#kindmaybetype) and using it will be possible to apply a sequence of operations in the case of the wrapped value has the expected kind.
212
278
 
213
- collection = [ {number: 1}, 'number 2', {number: 3}, :number_4 ]
279
+ ```ruby
280
+ Double = ->(value) do
281
+ Kind::Numeric.maybe(value)
282
+ .then { |number| number * 2 }
283
+ .value_or(0)
284
+ end
214
285
 
215
- collection.select(&Kind.of.Hash?) # [{:number=>1}, {:number=>3}]
286
+ Double.('2') # 0
287
+ Double.(2) # 4
216
288
  ```
217
289
 
218
- ### Kind.is.\<Type\>() - Verifying if some class/module is the expected kind.
219
-
220
- You can use `Kind.is` to verify if some class has the expected type as its ancestor.
290
+ If it is invoked without arguments, it returns the typed Maybe. But, if it receives arguments, it will behave like the `Kind::Maybe.wrap` method. e.g.
221
291
 
222
292
  ```ruby
223
- Kind.is.Hash(String) # false
293
+ Kind::Integer.maybe #<Kind::Maybe::Typed:0x0000... @kind=Kind::Integer>
224
294
 
225
- Kind.is.Hash(Hash) # true
295
+ Kind::Integer.maybe(0).some? # true
296
+ Kind::Integer.maybe { 1 }.some? # true
297
+ Kind::Integer.maybe(2) { |n| n * 2 }.some? # true
226
298
 
227
- Kind.is.Hash(ActiveSupport::HashWithIndifferentAccess) # true
299
+ Kind::Integer.maybe { 2 / 0 }.none? # true
300
+ Kind::Integer.maybe(2) { |n| n / 0 }.none? # true
301
+ Kind::Integer.maybe('2') { |n| n * n }.none? # true
228
302
  ```
229
303
 
230
- And just for convenience, you can use the method `Kind.of.*.class?` to verify if the given class has the expected type as its ancestor.
304
+ > **Note:** You can use `Kind::\<Type\>.optional` as an alias for `Kind::\<Type\>.maybe`.
305
+
306
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
307
+
308
+ ### Kind::\<Type\>?
309
+
310
+ There is a second way to do a type verification and know if one or multiple values has the expected type. You can use the predicate kind methods (`Kind::Hash?`). e.g:
231
311
 
232
312
  ```ruby
233
- Kind.of.Hash.class?(Hash) # true
313
+ # Verifying one value
314
+ Kind::Enumerable?({}) # true
234
315
 
235
- Kind.of.Hash.class?(ActiveSupport::HashWithIndifferentAccess) # true
316
+ # Verifying multiple values
317
+ Kind::Enumerable?({}, [], Set.new) # true
236
318
  ```
237
319
 
238
- > **Note:** The `Kind.is` could check the inheritance of Classes/Modules.
320
+ Like the `Kind::<Type>.value?` method, if the `Kind::<Type>?` doesn't receive an argument, it will return a lambda which will know how to perform the kind verification.
239
321
 
240
322
  ```ruby
241
- #
242
- # Verifying if the attribute value is the class or a subclass.
243
- #
244
- class Human; end
245
- class Person < Human; end
246
- class User < Human; end
323
+ collection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]
247
324
 
248
- Kind.is(Human, User) # true
249
- Kind.is(Human, Human) # true
250
- Kind.is(Human, Person) # true
325
+ collection.select(&Kind::Enumerable?) # [{:number=>1}, {:number=>3}, [:number, 5]]
326
+ ```
251
327
 
252
- Kind.is(Human, Struct) # false
328
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
253
329
 
254
- #
255
- # Verifying if the attribute value is the module or if it is a class that includes the module
256
- #
257
- module Human; end
258
- class Person; include Human; end
259
- class User; include Human; end
330
+ ### Kind::{Array,Hash,String,Set}.value_or_empty()
260
331
 
261
- Kind.is(Human, User) # true
262
- Kind.is(Human, Human) # true
263
- Kind.is(Human, Person) # true
332
+ This method is available for some type checkers (`Kind::Array`, `Kind::Hash`, `Kind::String`, `Kind::Set`), and it will return an empty frozen value if the given one hasn't the expected kind.
333
+ ```ruby
334
+ Kind::Array.value_or_empty({}) # []
335
+ Kind::Array.value_or_empty({}).frozen? # true
336
+ ```
264
337
 
265
- Kind.is(Human, Struct) # false
338
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
266
339
 
267
- #
268
- # Verifying if the attribute value is the module or if it is a module that extends the module
269
- #
270
- module Human; end
271
- module Person; extend Human; end
272
- module User; extend Human; end
340
+ ### List of all type checkers
273
341
 
274
- Kind.is(Human, User) # true
275
- Kind.is(Human, Human) # true
276
- Kind.is(Human, Person) # true
342
+ #### Core
277
343
 
278
- Kind.is(Human, Struct) # false
279
- ```
344
+ * `Kind::Array`
345
+ * `Kind::Class`
346
+ * `Kind::Comparable`
347
+ * `Kind::Enumerable`
348
+ * `Kind::Enumerator`
349
+ * `Kind::File`
350
+ * `Kind::Float`
351
+ * `Kind::Hash`
352
+ * `Kind::Integer`
353
+ * `Kind::IO`
354
+ * `Kind::Method`
355
+ * `Kind::Module`
356
+ * `Kind::Numeric`
357
+ * `Kind::Proc`
358
+ * `Kind::Queue`
359
+ * `Kind::Range`
360
+ * `Kind::Regexp`
361
+ * `Kind::String`
362
+ * `Kind::Struct`
363
+ * `Kind::Symbol`
364
+ * `Kind::Time`
280
365
 
281
- [⬆️ Back to Top](#table-of-contents-)
366
+ #### Stdlib
282
367
 
283
- ### How to create a new type checker?
368
+ * `Kind::OpenStruct`
369
+ * `Kind::Set`
284
370
 
285
- There are two ways to do this, you can create type checkers dynamically or register new ones.
371
+ #### Custom
286
372
 
287
- #### Creating/Verifiyng type checkers dynamically
373
+ * `Kind::Boolean`
374
+ * `Kind::Callable`
375
+ * `Kind::Lambda`
376
+
377
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
378
+
379
+ ### Creating type checkers
380
+
381
+ There are two ways to do this, you can create type checkers dynamically or defining a module.
382
+
383
+ #### Dynamic creation
384
+
385
+ ##### Using a class or a module
288
386
 
289
387
  ```ruby
290
388
  class User
@@ -292,365 +390,486 @@ end
292
390
 
293
391
  user = User.new
294
392
 
295
- # ------------------------ #
296
- # Verifiyng the value kind #
297
- # ------------------------ #
298
-
299
- Kind.of(User, user) # <User ...>
300
- Kind.of(User, {}) # Kind::Error ({} expected to be a kind of User)
393
+ kind_of_user = Kind::Of(User)
301
394
 
302
- Kind.of(Hash, {}) # {}
303
- Kind.of(Hash, user) # Kind::Error (<User ...> expected to be a kind of Hash)
395
+ # kind_of_user.name
396
+ # kind_of_user.kind
397
+ # The type checker can return its kind and its name
398
+ kind_of_user.name # "User"
399
+ kind_of_user.kind # User
304
400
 
305
- # ----------------------------------------- #
306
- # Verifiyng if the value is a kind instance #
307
- # ----------------------------------------- #
401
+ # kind_of_user.===
402
+ # Can check if a given value is an instance of its kind.
403
+ kind_of_user === 0 # false
404
+ kind_of_user === User.new # true
308
405
 
309
- Kind.of?(Numeric, 1) # true
310
- Kind.of?(Numeric, 1, 2.0) # true
406
+ # kind_of_user.value?(value)
407
+ # Can check if a given value is an instance of its kind.
408
+ kind_of_user.value?('') # false
409
+ kind_of_user.value?(User.new) # true
311
410
 
312
- Kind.of?(Numeric, '1') # false
313
- Kind.of?(Numeric, 1, '2.0') # false
411
+ # If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.
412
+ [0, User.new].select?(&kind_of_user.value?) # [#<User:0x0000.... >]
314
413
 
315
- # Note: Kind.of?(Type) without arguments will return a
316
- # lambda that will perform an instance verification
317
- #
318
- [1, '2', 3.0, '4']
319
- .select(&Kind.of?(Numeric)) # [1, 3.0]
414
+ # kind_of_user.or_nil(value)
415
+ # Can return nil if the given value isn't an instance of its kind
416
+ kind_of_user.or_nil({}) # nil
417
+ kind_of_user.or_nil(User.new) # #<User:0x0000.... >
320
418
 
321
- # ---------------------------------- #
322
- # Creating type checkers dynamically #
323
- # ---------------------------------- #
419
+ # kind_of_user.or_undefined(value)
420
+ # Can return Kind::Undefined if the given value isn't an instance of its kind
421
+ kind_of_user.or_undefined([]) # Kind::Undefined
422
+ kind_of_user.or_undefined(User.new) # #<User:0x0000.... >
324
423
 
325
- kind_of_user = Kind.of(User)
424
+ # kind_of_user.or(fallback, value)
425
+ # Can return a fallback if the given value isn't an instance of its kind
426
+ kind_of_user.or(nil, 0) # nil
427
+ kind_of_user.or(nil, User.new) # #<User:0x0000.... >
326
428
 
327
- kind_of_user.or_nil({}) # nil
429
+ # If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.
430
+ [1, User.new].map(&kind_of_user.or(nil)) # [nil, #<User:0x0000.... >]
328
431
 
329
- kind_of_user.instance?({}) # false
330
- kind_of_user.instance?(User) # true
432
+ # An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.
433
+ [0, User.new].map(&kind_of_user.or(:foo)) # Kind::Error (:foo expected to be a kind of User)
331
434
 
332
- kind_of_user.class?(Hash) # false
333
- kind_of_user.class?(User) # true
435
+ # kind_of_user[value]
436
+ # Will raise Kind::Error if the given value isn't an instance of the expected kind
437
+ kind_of_user[:foo] # Kind::Error (:foo expected to be a kind of User)
438
+ kind_of_user[User.new] # #<User:0x0000.... >
334
439
 
335
- # ------------------------------------ #
336
- # Using methods which returns a lambda #
337
- # ------------------------------------ #
338
- collection = [User.new, User.new, 0, {} nil, User.new]
440
+ # kind_of_user.value(arg, default:)
441
+ # This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
442
+ kind_of_user.value(User.new, default: User.new) # #<User:0x0000...>
339
443
 
340
- collection.select(&Kind.of(User).instance?).size == 3 # true
444
+ kind_of_user.value('1', default: User.new) # #<User:0x0000...>
341
445
 
342
- collection.map(&Kind.of(User).as_optional).select(&:some?).size == 3 # true
446
+ kind_of_user.value('1', default: 1) # Kind::Error (1 expected to be a kind of User)
343
447
 
344
- # Creating type checkers dynamically is cheap
345
- # because a singleton object is created to be available for use.
448
+ # kind_of_user.maybe
449
+ # This method returns a typed Kind::Maybe.
450
+ kind_of_user.maybe('1').value_or(User.new) # #<User:0x0000...>
451
+ ```
346
452
 
347
- kind_of_user.object_id == Kind.of(User).object_id # true
453
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
348
454
 
349
- # --------------------------------------------- #
350
- # Kind.is() can be used to check a class/module #
351
- # --------------------------------------------- #
455
+ ##### Using an object which responds to ===
352
456
 
353
- class AdminUser < User
354
- end
457
+ Example using a lambda (an object which responds to .===) and a hash with the kind name.
355
458
 
356
- Kind.is(User, AdminUser) # true
459
+ ```ruby
460
+ PositiveInteger = Kind::Of(
461
+ -> value { value.kind_of?(Integer) && value > 0 },
462
+ name: 'PositiveInteger'
463
+ )
464
+
465
+ # PositiveInteger.name
466
+ # PositiveInteger.kind
467
+ # The type checker can return its kind and its name
468
+ PositiveInteger.name # "PositiveInteger"
469
+ PositiveInteger.kind # #<Proc:0x0000.... >
470
+
471
+ # PositiveInteger.===
472
+ # Can check if a given value is an instance of its kind.
473
+ PositiveInteger === 1 # true
474
+ PositiveInteger === 0 # false
475
+
476
+ # PositiveInteger.value?(value)
477
+ # Can check if a given value is an instance of its kind.
478
+ PositiveInteger.value?(1) # true
479
+ PositiveInteger.value?(-1) # false
480
+
481
+ # If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.
482
+ [1, 2, 0, 3, -1].select?(&PositiveInteger.value?) # [1, 2, 3]
483
+
484
+ # PositiveInteger.or_nil(value)
485
+ # Can return nil if the given value isn't an instance of its kind
486
+ PositiveInteger.or_nil(1) # 1
487
+ PositiveInteger.or_nil(0) # nil
488
+
489
+ # PositiveInteger.or_undefined(value)
490
+ # Can return Kind::Undefined if the given value isn't an instance of its kind
491
+ PositiveInteger.or_undefined(2) # 2
492
+ PositiveInteger.or_undefined(-1) # Kind::Undefined
493
+
494
+ # PositiveInteger.or(fallback, value)
495
+ # Can return a fallback if the given value isn't an instance of its kind
496
+ PositiveInteger.or(nil, 1) # 1
497
+ PositiveInteger.or(nil, 0) # nil
498
+
499
+ # If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.
500
+ [1, 2, 0, 3, -1].map(&PositiveInteger.or(1)) # [1, 2, 1, 3, 1]
501
+ [1, 2, 0, 3, -1].map(&PositiveInteger.or(nil)) # [1, 2, nil, 3, nil]
502
+
503
+ # An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.
504
+ [1, 2, 0, 3, -1].map(&PositiveInteger.or(:foo)) # Kind::Error (:foo expected to be a kind of PositiveInteger)
505
+
506
+ # PositiveInteger[value]
507
+ # Will raise Kind::Error if the given value isn't an instance of the expected kind
508
+ PositiveInteger[1] # 1
509
+ PositiveInteger[:foo] # Kind::Error (:foo expected to be a kind of PositiveInteger)
510
+
511
+ # PositiveInteger.value(arg, default:)
512
+ # This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
513
+ PositiveInteger.value(2, default: 1) # 2
514
+
515
+ PositiveInteger.value('1', default: 1) # 1
516
+
517
+ PositiveInteger.value('1', default: 0) # Kind::Error (0 expected to be a kind of PositiveInteger)
518
+
519
+ # PositiveInteger.maybe
520
+ # This method returns a typed Kind::Maybe.
521
+ PositiveInteger.maybe(0).value_or(1) # 1
522
+
523
+ PositiveInteger.maybe(2).value_or(1) # 2
357
524
  ```
358
525
 
359
- #### Registering new (custom) type checker
526
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
360
527
 
361
- Use `Kind::Types.add()`. e.g:
528
+ #### Kind::<Type> module
529
+
530
+ The idea here is to create a type checker inside of the `Kind` namespace, so to do this, you will only need to use pure Ruby. e.g:
362
531
 
363
532
  ```ruby
364
533
  class User
365
534
  end
366
535
 
367
- # You can define it at the end of the file class/module where class/module was declared.
368
-
369
- Kind::Types.add(User)
536
+ module Kind
537
+ module User
538
+ extend self, TypeChecker
370
539
 
371
- # Or, you can add the type checker within the class definition.
540
+ # Define the expected kind of this type checker.
541
+ def kind; ::User; end
542
+ end
372
543
 
373
- class User
374
- Kind::Types.add(self)
544
+ # This how the Kind::<Type>? methods are defined.
545
+ def self.User?(*values)
546
+ KIND.of?(::User, values)
547
+ end
375
548
  end
376
549
 
377
- # --------------- #
378
- # Usage examples: #
379
- # --------------- #
380
-
381
- Kind.of.User(User.new) # #<User:0x0000...>
550
+ # Doing this you will have the same methods of a standard type checker (like: `Kind::Symbol`).
382
551
 
383
- Kind.of.User({}) # Kind::Error ({} expected to be a kind of User)
384
-
385
- Kind.of.User.or_nil({}) # nil
552
+ user = User.new
386
553
 
387
- Kind.of.User.instance?({}) # false
388
- Kind.of.User.instance?(User) # true
554
+ Kind::User[user] # #<User:0x0000...>
555
+ Kind::User[{}] # Kind::Error ({} expected to be a kind of User)
389
556
 
390
- Kind.of.User.class?(Hash) # false
391
- Kind.of.User.class?(User) # true
557
+ Kind::User?(user) # true
558
+ Kind::User?({}) # false
392
559
  ```
393
560
 
394
- [⬆️ Back to Top](#table-of-contents-)
395
-
396
- ##### What happens if a custom type checker has a namespace?
397
-
398
- The type checker will preserve the namespace. ;)
399
-
400
- ```ruby
401
- module Account
402
- class User
403
- Kind::Types.add(self)
404
- end
405
- end
561
+ The advantages of this approach are:
406
562
 
407
- module Account
408
- class User
409
- class Membership
410
- Kind::Types.add(self)
411
- end
412
- end
413
- end
563
+ 1. You will have a singleton (a unique instance) to be used, so the garbage collector will work less.
564
+ 2. You can define additional methods to be used with this kind.
414
565
 
415
- Kind.of.Account::User({}) # Kind::Error ({} expected to be a kind of Account::User)
566
+ The disadvantage is:
416
567
 
417
- Kind.of.Account::User(Account::User.new) # #<Account::User:0x0000...>
568
+ 1. You could overwrite some standard type checker or constant. I believe that this will be hard to happen, but must be your concern if you decide to use this kind of approach.
418
569
 
419
- Kind.of.Account::User.or_nil({}) # nil
570
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
420
571
 
421
- Kind.of.Account::User.instance?({}) # false
422
- Kind.of.Account::User.instance?(Account::User.new) # true
572
+ ### Utility methods
423
573
 
424
- Kind.of.Account::User.class?(Hash) # false
425
- Kind.of.Account::User.class?(Account::User) # true
574
+ #### Kind.of_class?()
426
575
 
427
- # ---
576
+ This method verify if a given value is a `Class`.
428
577
 
429
- Kind.of.Account::User::Membership({}) # Kind::Error ({} expected to be a kind of Account::User::Membership)
578
+ ```ruby
579
+ Kind.of_class?(Hash) # true
580
+ Kind.of_class?(Enumerable) # false
581
+ Kind.of_class?(1) # false
582
+ ```
430
583
 
431
- Kind.of.Account::User::Membership(Account::User::Membership.new) # #<Account::User::Membership:0x0000...>
584
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
432
585
 
433
- Kind.of.Account::User::Membership.or_nil({}) # nil
586
+ #### Kind.of_module?()
434
587
 
435
- Kind.of.Account::User::Membership.instance?({}) # false
436
- Kind.of.Account::User::Membership.instance?(Account::User::Membership.new) # true
588
+ This method verify if a given value is a `Module`.
437
589
 
438
- Kind.of.Account::User::Membership.class?(Hash) # false
439
- Kind.of.Account::User::Membership.class?(Account::User::Membership) # true
590
+ ```ruby
591
+ Kind.of_module?(Hash) # false
592
+ Kind.of_module?(Enumerable) # true
593
+ Kind.of_module?(1) # false
440
594
  ```
441
595
 
442
- [⬆️ Back to Top](#table-of-contents-)
596
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
597
+
598
+ #### Kind.of_module_or_class()
443
599
 
444
- ## Type checkers
600
+ This method return the given value if it is a module or a class. If not, a `Kind::Error` will be raised.
445
601
 
446
- The list of types (classes and modules) available to use with `Kind.of.*` or `Kind.is.*` are:
602
+ ```ruby
603
+ Kind.of_module_or_class(String) # String
604
+ Kind.of_module_or_class(1) # Kind::Error (1 expected to be a kind of Module/Class)
605
+ ```
447
606
 
448
- ### Classes' type checkers
607
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
449
608
 
450
- - `Kind.of.String`
451
- - `Kind.of.Symbol`
452
- - `Kind.of.Numeric`
453
- - `Kind.of.Integer`
454
- - `Kind.of.Float`
455
- - `Kind.of.Regexp`
456
- - `Kind.of.Time`
457
- - `Kind.of.Array`
458
- - `Kind.of.Range`
459
- - `Kind.of.Hash`
460
- - `Kind.of.Struct`
461
- - `Kind.of.Enumerator`
462
- - `Kind.of.Set`
463
- - `Kind.of.Method`
464
- - `Kind.of.Proc`
465
- - `Kind.of.IO`
466
- - `Kind.of.File`
609
+ #### Kind.respond_to()
467
610
 
468
- ### Modules' type checkers
611
+ this method returns the given object if it responds to all of the method names. But if the object does not respond to some of the expected methods, an error will be raised.
612
+ ```ruby
613
+ Kind.respond_to('', :upcase) # ""
614
+ Kind.respond_to('', :upcase, :strip) # ""
469
615
 
470
- - `Kind.of.Enumerable`
471
- - `Kind.of.Comparable`
616
+ Kind.respond_to(1, :upcase) # expected 1 to respond to :upcase
617
+ Kind.respond_to(2, :to_s, :upcase) # expected 2 to respond to :upcase
618
+ ```
472
619
 
473
- ### Specials' type checkers
620
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
474
621
 
475
- - `Kind.of.Class()`
476
- - `Kind.of.Module()`
477
- - `Kind.of.Lambda()`
478
- - `Kind.of.Boolean()`
479
- - `Kind.of.Callable()`: verifies if the given value `respond_to?(:call)`.
480
- - `Kind.of.Maybe()` or its alias `Kind.of.Optional()`
622
+ #### Kind.of()
481
623
 
482
- **Note:** Remember, you can use the `Kind.is.*` method to check if some given value is a class/module with all type checkers above.
624
+ There is a second way to do a strict type verification, you can use the `Kind.of()` method to do this. It receives the kind as the first argument and the value to be checked as the second one.
625
+ ```ruby
626
+ Kind.of(Hash, {}) # {}
627
+ Kind.of(Hash, []) # Kind::Error ([] expected to be a kind of Hash)
628
+ ```
483
629
 
484
- [⬆️ Back to Top](#table-of-contents-)
630
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
485
631
 
486
- ## Kind::Validator (ActiveModel::Validations)
632
+ #### Kind.of?()
487
633
 
488
- This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, < 6.1.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
634
+ This method can be used to check if one or multiple values have the expected kind.
489
635
 
490
636
  ```ruby
491
- class Person
492
- include ActiveModel::Validations
637
+ # Checking one value
638
+ Kind.of?(Array, []) # true
639
+ Kind.of?(Array, {}) # false
640
+
641
+ # Checking multiple values
642
+ Kind.of?(Enumerable, [], {}) # true
643
+ Kind.of?(Hash, {}, {}) # true
644
+ Kind.of?(Array, [], {}) # false
645
+ ```
493
646
 
494
- attr_accessor :first_name, :last_name
647
+ If the method receives only the first argument (the kind) a lambda will be returned and it will know how to do the type verification.
495
648
 
496
- validates :first_name, :last_name, kind: String
497
- end
649
+ ```ruby
650
+ [1, '2', 3].select(&Kind.of?(Numeric)) # [1, 3]
498
651
  ```
499
652
 
500
- And to make use of it, you will need to do an explicitly require. e.g:
653
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
501
654
 
655
+ #### Kind.value()
656
+
657
+ This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.
502
658
  ```ruby
503
- # In some Gemfile
504
- gem 'kind', require: 'kind/active_model/validation'
659
+ Kind.value(String, '1', default: '') # "1"
505
660
 
506
- # In some .rb file
507
- require 'kind/active_model/validation'
661
+ Kind.value(String, 1, default: '') # ""
662
+
663
+ Kind.value(String, 1, default: 2) # Kind::Error (2 expected to be a kind of String)
508
664
  ```
509
665
 
510
- ### Usage
666
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
667
+
668
+ #### Kind.is()
511
669
 
512
- **[Object#kind_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-kind_of-3F)**
670
+ You can use `Kind.is` to verify if some class has the expected type as its ancestor.
513
671
 
514
672
  ```ruby
515
- validates :name, kind: { of: String }
673
+ Kind.is(Hash, String) # false
516
674
 
517
- # Use an array to verify if the attribute
518
- # is an instance of one of the classes/modules.
675
+ Kind.is(Hash, Hash) # true
519
676
 
520
- validates :status, kind: { of: [String, Symbol]}
677
+ Kind.is(Enumerable, Hash) # true
521
678
  ```
522
679
 
523
- **[Kind.is](#verifying-the-kind-of-some-classmodule)**
680
+ The `Kind.is` also could check the inheritance of Classes/Modules.
524
681
 
525
682
  ```ruby
526
683
  #
527
- # Verifying if the attribute value is the class or a subclass.
684
+ # Verifying if a class is or inherits from the expected class.
528
685
  #
529
686
  class Human; end
530
687
  class Person < Human; end
531
688
  class User < Human; end
532
689
 
533
- validates :human_kind, kind: { is: Human }
690
+ Kind.is(Human, User) # true
691
+ Kind.is(Human, Human) # true
692
+ Kind.is(Human, Person) # true
693
+
694
+ Kind.is(Human, Struct) # false
534
695
 
535
696
  #
536
- # Verifying if the attribute value is the module or if it is a class that includes the module
697
+ # Verifying if the classes included a module.
537
698
  #
538
699
  module Human; end
539
700
  class Person; include Human; end
540
701
  class User; include Human; end
541
702
 
542
- validates :human_kind, kind: { is: Human }
703
+ Kind.is(Human, User) # true
704
+ Kind.is(Human, Human) # true
705
+ Kind.is(Human, Person) # true
706
+
707
+ Kind.is(Human, Struct) # false
543
708
 
544
709
  #
545
- # Verifying if the attribute value is the module or if it is a module that extends the module
710
+ # Verifying if a class is or inherits from the expected module.
546
711
  #
547
712
  module Human; end
548
713
  module Person; extend Human; end
549
714
  module User; extend Human; end
550
715
 
551
- validates :human_kind, kind: { is: Human }
552
-
553
- # or use an array to verify if the attribute
554
- # is a kind of one those classes/modules.
716
+ Kind.is(Human, User) # true
717
+ Kind.is(Human, Human) # true
718
+ Kind.is(Human, Person) # true
555
719
 
556
- validates :human_kind, kind: { is: [Person, User] }
720
+ Kind.is(Human, Struct) # false
557
721
  ```
558
722
 
559
- **[Object#instance_of?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-instance_of-3F)**
723
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
724
+
725
+ ### Utility modules
726
+
727
+ #### Kind::Try
728
+
729
+ The method `.call` of this module invokes a public method with or without arguments like `public_send` does, except that if the receiver does not respond to it the call returns `nil` rather than raising an exception.
560
730
 
561
731
  ```ruby
562
- validates :name, kind: { instance_of: String }
732
+ Kind::Try.(' foo ', :strip) # "foo"
733
+ Kind::Try.({a: 1}, :[], :a) # 1
734
+ Kind::Try.({a: 1}, :[], :b) # nil
735
+ Kind::Try.({a: 1}, :fetch, :b, 2) # 2
563
736
 
564
- # or use an array to verify if the attribute
565
- # is an instance of one of the classes/modules.
737
+ Kind::Try.(:symbol, :strip) # nil
738
+ Kind::Try.(:symbol, :fetch, :b, 2) # nil
566
739
 
567
- validates :name, kind: { instance_of: [String, Symbol] }
740
+ # It raises an exception if the method name isn't a string or a symbol
741
+ Kind::Try.({a: 1}, 1, :a) # TypeError (1 is not a symbol nor a string)
568
742
  ```
569
743
 
570
-
571
- **[Object#respond_to?](https://ruby-doc.org/core-2.6.4/Object.html#method-i-respond_to-3F)**
744
+ This module has the method `[]` that knows how to create a lambda that will know how to perform the `try` strategy.
572
745
 
573
746
  ```ruby
574
- validates :handler, kind: { respond_to: :call }
747
+ results =
748
+ [
749
+ {},
750
+ {name: 'Foo Bar'},
751
+ {name: 'Rodrigo Serradura'},
752
+ ].map(&Kind::Try[:fetch, :name, 'John Doe'])
753
+
754
+ p results # ["John Doe", "Foo Bar", "Rodrigo Serradura"]
575
755
  ```
576
756
 
577
- **Array.new.all? { |item| item.kind_of?(Class) }**
757
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
758
+
759
+ #### Kind::Dig
760
+
761
+ The method `.call` of this module has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some step can't be digged.
578
762
 
579
763
  ```ruby
580
- validates :account_types, kind: { array_of: String }
764
+ s = Struct.new(:a, :b).new(101, 102)
765
+ o = OpenStruct.new(c: 103, d: 104)
766
+ d = { struct: s, ostruct: o, data: [s, o]}
581
767
 
582
- # or use an array to verify if the attribute
583
- # is an instance of one of the classes
768
+ Kind::Dig.(s, [:a]) # 101
769
+ Kind::Dig.(o, [:c]) # 103
584
770
 
585
- validates :account_types, kind: { array_of: [String, Symbol] }
771
+ Kind::Dig.(d, [:struct, :b]) # 102
772
+ Kind::Dig.(d, [:data, 0, :b]) # 102
773
+ Kind::Dig.(d, [:data, 0, 'b']) # 102
774
+
775
+ Kind::Dig.(d, [:ostruct, :d]) # 104
776
+ Kind::Dig.(d, [:data, 1, :d]) # 104
777
+ Kind::Dig.(d, [:data, 1, 'd']) # 104
778
+
779
+ Kind::Dig.(d, [:struct, :f]) # nil
780
+ Kind::Dig.(d, [:ostruct, :f]) # nil
781
+ Kind::Dig.(d, [:data, 0, :f]) # nil
782
+ Kind::Dig.(d, [:data, 1, :f]) # nil
586
783
  ```
587
784
 
588
- **Array.new.all? { |item| expected_values.include?(item) }**
785
+ Another difference between the `Kind::Dig` and the native Ruby dig, is that it knows how to extract values from regular objects.
589
786
 
590
787
  ```ruby
591
- # Verifies if the attribute value
592
- # is an array with some or all the expected values.
788
+ class Person
789
+ attr_reader :name
593
790
 
594
- validates :account_types, kind: { array_with: ['foo', 'bar'] }
595
- ```
791
+ def initialize(name)
792
+ @name = name
793
+ end
794
+ end
596
795
 
597
- #### Defining the default validation strategy
796
+ person = Person.new('Rodrigo')
598
797
 
599
- By default, you can define the attribute type directly (without a hash). e.g.
798
+ Kind::Dig.(person, [:name]) # "Rodrigo"
600
799
 
601
- ```ruby
602
- validates :name, kind: String
603
- # or
604
- validates :name, kind: [String, Symbol]
800
+ Kind::Dig.({people: [person]}, [:people, 0, :name]) # "Rodrigo"
605
801
  ```
606
802
 
607
- To changes this behavior you can set another strategy to validates the attributes types:
803
+ This module has the method `[]` that knows how to create a lambda that will know how to perform the `dig` strategy.
608
804
 
609
805
  ```ruby
610
- Kind::Validator.default_strategy = :instance_of
806
+ results = [
807
+ { person: {} },
808
+ { person: { name: 'Foo Bar'} },
809
+ { person: { name: 'Rodrigo Serradura'} },
810
+ ].map(&Kind::Dig[:person, :name])
611
811
 
612
- # Tip: Create an initializer if you are in a Rails application.
812
+ p results # [nil, "Foo Bar", "Rodrigo Serradura"],
613
813
  ```
614
814
 
615
- And these are the available options to define the default strategy:
616
- - `kind_of` *(default)*
617
- - `instance_of`
815
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
618
816
 
619
- #### Using the `allow_nil` and `strict` options
817
+ #### Kind::Presence
620
818
 
621
- You can use the `allow_nil` option with any of the kind validations. e.g.
819
+ The method `.call` of this module returns the given value if it's present otherwise it will return `nil`.
622
820
 
623
821
  ```ruby
624
- validates :name, kind: String, allow_nil: true
822
+ Kind::Presence.(true) # true
823
+ Kind::Presence.('foo') # "foo"
824
+ Kind::Presence.([1, 2]) # [1, 2]
825
+ Kind::Presence.({a: 3}) # {a: 3}
826
+ Kind::Presence.(Set.new([4])) # #<Set: {4}>
827
+
828
+ Kind::Presence.('') # nil
829
+ Kind::Presence.(' ') # nil
830
+ Kind::Presence.("\t\n\r") # nil
831
+ Kind::Presence.("\u00a0") # nil
832
+
833
+ Kind::Presence.([]) # nil
834
+ Kind::Presence.({}) # nil
835
+ Kind::Presence.(Set.new) # nil
836
+
837
+ Kind::Presence.(nil) # nil
838
+ Kind::Presence.(false) # nil
839
+
840
+ # nil will be returned if the given object responds to the method blank? and this method result is true.
841
+ MyObject = Struct.new(:is_blank) do
842
+ def blank?
843
+ is_blank
844
+ end
845
+ end
846
+
847
+ my_object = MyObject.new
848
+
849
+ my_object.is_blank = true
850
+
851
+ Kind::Presence.(my_object) # nil
852
+
853
+ my_object.is_blank = false
854
+
855
+ Kind::Presence.(my_object) # #<struct MyObject is_blank=false>
625
856
  ```
626
857
 
627
- And as any active model validation, kind validations works with the `strict: true`
628
- option and with the `validates!` method. e.g.
858
+ This module also has the method `to_proc`, because of this you can make use of the `Kind::Presence` in methods that receive a block as an argument. e.g:
629
859
 
630
860
  ```ruby
631
- validates :first_name, kind: String, strict: true
632
- # or
633
- validates! :last_name, kind: String
861
+ ['', [], {}, '1', [2]].map(&Kind::Presence) # [nil, nil, nil, "1", [2]]
634
862
  ```
635
863
 
636
- [⬆️ Back to Top](#table-of-contents-)
864
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
637
865
 
638
866
  ## Kind::Undefined
639
867
 
640
- The [`Kind::Undefined`](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind/undefined.rb) constant is used as the default argument of type checkers. This is necessary [to know if no arguments were passed to the type check methods](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind.rb#L45-L48). But, you can use it in your codebase too, especially if you need to distinguish the usage of `nil` as a method argument.
641
-
642
- If you are interested, check out [the tests](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/test/kind/undefined_test.rb) to understand its methods.
643
-
644
- ### Kind.of.\<Type\>.or_undefined()
645
-
646
- If you interested in use `Kind::Undefined` you can use the method `.or_undefined` with any of the [available type checkers](#type-checkers). e.g:
868
+ The [`Kind::Undefined`](https://github.com/serradura/kind/blob/1674bab/lib/kind/undefined.rb) constant can be used to distinguish the usage of `nil`.
647
869
 
648
- ```ruby
649
- Kind.of.String.or_undefined(nil) # Kind::Undefined
650
- Kind.of.String.or_undefined("something") # "something"
651
- ```
870
+ If you are interested, check out [the tests](https://github.com/serradura/kind/blob/main/test/kind/undefined_test.rb) to understand its methods.
652
871
 
653
- [⬆️ Back to Top](#table-of-contents-)
872
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
654
873
 
655
874
  ## Kind::Maybe
656
875
 
@@ -699,13 +918,15 @@ puts optional.value_or(1) # 1
699
918
  puts optional.value_or { 1 } # 1
700
919
  ```
701
920
 
921
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
922
+
702
923
  #### Replacing blocks by lambdas
703
924
 
704
925
  ```ruby
705
926
  Add = -> params do
706
- a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
927
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
707
928
 
708
- a + b if Kind.of.Numeric?(a, b)
929
+ a + b if Kind::Numeric?(a, b)
709
930
  end
710
931
 
711
932
  # --
@@ -719,6 +940,8 @@ end
719
940
  Kind::Maybe.new(nil).map(&Add).value_or(0) # 0
720
941
  ```
721
942
 
943
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
944
+
722
945
  ### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases
723
946
 
724
947
  You can use `Kind::Maybe[]` (brackets) instead of the `.new` to transform values in a `Kind::Maybe`. Another alias is `.then` to the `.map` method.
@@ -746,13 +969,15 @@ result =
746
969
  puts result # 42
747
970
  ```
748
971
 
972
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
973
+
749
974
  #### Replacing blocks by lambdas
750
975
 
751
976
  ```ruby
752
977
  Add = -> params do
753
- a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
978
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
754
979
 
755
- a + b if Kind.of.Numeric.instance?(a, b)
980
+ a + b if Kind::Numeric?(a, b)
756
981
  end
757
982
 
758
983
  # --
@@ -766,16 +991,24 @@ Kind::Maybe['2'].then(&Add).value_or(0) # 0
766
991
  Kind::Maybe[nil].then(&Add).value_or(0) # 0
767
992
  ```
768
993
 
994
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
995
+
769
996
  ### Kind::None() and Kind::Some()
770
997
 
771
998
  If you need to ensure the return of `Kind::Maybe` results from your methods/lambdas,
772
999
  you could use the methods `Kind::None` and `Kind::Some` to do this. e.g:
773
1000
 
774
1001
  ```ruby
1002
+ Double = ->(arg) do
1003
+ number = Kind::Numeric.or_nil(arg)
1004
+
1005
+ Kind::Maybe[number].then { |number| number * 2 }
1006
+ end
1007
+
775
1008
  Add = -> params do
776
- a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
1009
+ a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)
777
1010
 
778
- return Kind::None unless Kind.of.Numeric?(a, b)
1011
+ return Kind::None unless Kind::Numeric?(a, b)
779
1012
 
780
1013
  Kind::Some(a + b)
781
1014
  end
@@ -794,40 +1027,14 @@ Add.call(a:1, b: 2) # #<Kind::Maybe::Some:0x0000... @value=3>
794
1027
  Kind::Maybe[a: 1, b: 2].then(&Add).value_or(0) # 3
795
1028
 
796
1029
  Kind::Maybe[1].then(&Add).value_or(0) # 0
797
- Kind::Maybe['2'].then(&Add).value_or(0) # 0
798
- Kind::Maybe[nil].then(&Add).value_or(0) # 0
799
- ```
800
1030
 
801
- ### Kind.of.Maybe()
802
-
803
- You can use the `Kind.of.Maybe()` to know if the given value is a kind of `Kind::Maybe`object. e.g:
804
-
805
- ```ruby
806
- def double(maybe_number)
807
- Kind.of.Maybe(maybe_number)
808
- .map { |value| value * 2 }
809
- .value_or(0)
810
- end
811
-
812
- number = Kind::Maybe[4]
813
-
814
- puts double(number) # 8
815
-
816
- # -------------------------------------------------------#
817
- # All the type checker methods are available to use too. #
818
- # -------------------------------------------------------#
819
-
820
- Kind.of.Maybe.instance?(number) # true
821
-
822
- Kind.of.Maybe.or_nil(number) # <Kind::Maybe::Some @value=4 ...>
823
-
824
- Kind.of.Maybe.instance(number) # <Kind::Maybe::Some @value=4 ...>
825
- Kind.of.Maybe.instance(4) # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
1031
+ # --
826
1032
 
827
- Kind.of.Maybe[number] # <Kind::Maybe::Some @value=4 ...>
828
- Kind.of.Maybe[4] # Kind::Error (4 expected to be a kind of Kind::Maybe::Result)
1033
+ Add.(a: 2, b: 2).then(&Double).value # 8
829
1034
  ```
830
1035
 
1036
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1037
+
831
1038
  ### Kind::Optional
832
1039
 
833
1040
  The `Kind::Optional` constant is an alias for `Kind::Maybe`. e.g:
@@ -853,103 +1060,117 @@ result2 =
853
1060
  puts result2 # 35
854
1061
  ```
855
1062
 
1063
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1064
+
856
1065
  #### Replacing blocks by lambdas
857
1066
 
858
1067
  ```ruby
859
- Add = -> params do
860
- a, b = Kind.of.Hash(params, or: Empty::HASH).values_at(:a, :b)
1068
+ Double = ->(arg) do
1069
+ number = Kind::Numeric.or_nil(arg)
861
1070
 
862
- a + b if Kind.of.Numeric.instance?(a, b)
1071
+ Kind::Maybe[number].then { |number| number * 2 }
863
1072
  end
864
1073
 
865
1074
  # --
866
1075
 
867
- Kind::Optional[a: 1, b: 2].then(&Add).value_or(0) # 3
1076
+ Kind::Optional[2].then(&Double).value_or(0) # 4
868
1077
 
869
1078
  # --
870
1079
 
871
- Kind::Optional[1].then(&Add).value_or(0) # 0
872
- Kind::Optional['2'].then(&Add).value_or(0) # 0
873
- Kind::Optional[nil].then(&Add).value_or(0) # 0
1080
+ Kind::Optional['2'].then(&Double).value_or(0) # 0
1081
+ Kind::Optional[nil].then(&Double).value_or(0) # 0
874
1082
  ```
875
1083
 
876
- **Note:** The `Kind.of.Optional` is available to check if some value is a `Kind::Optional`.
877
-
878
- [⬆️ Back to Top](#table-of-contents-)
1084
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
879
1085
 
880
- ### Kind.of.\<Type\>.as_optional
1086
+ ### Kind::Maybe(<Type>)
881
1087
 
882
- It is very common the need to avoid some computing when a method receives a wrong input.
883
- In these scenarios, you could check the given input type as optional and avoid unexpected behavior. e.g:
1088
+ You can use `Kind::Maybe(<Type>)` or `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
884
1089
 
885
1090
  ```ruby
886
- def person_name(params)
887
- Kind::Of::Hash
888
- .as_optional(params)
889
- .map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
890
- .map { |data| "#{data[:first_name]} #{data[:last_name]}" }
891
- .value_or { 'John Doe' }
892
- end
1091
+ result1 =
1092
+ Kind::Maybe(Numeric)
1093
+ .wrap(5)
1094
+ .then { |value| value * 5 }
1095
+ .value_or { 0 }
893
1096
 
894
- person_name('') # "John Doe"
895
- person_name(nil) # "John Doe"
1097
+ puts result1 # 25
896
1098
 
897
- person_name(first_name: 'Rodrigo') # "John Doe"
898
- person_name(last_name: 'Serradura') # "John Doe"
1099
+ # ---
899
1100
 
900
- person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
1101
+ result2 =
1102
+ Kind::Optional(Numeric)
1103
+ .wrap('5')
1104
+ .then { |value| value * 5 }
1105
+ .value_or { 0 }
901
1106
 
902
- #
903
- # See below the previous implementation without using an optional.
904
- #
905
- def person_name(params)
906
- if params.kind_of?(Hash) && params.values_at(:first_name, :last_name).compact.size == 2
907
- "#{params[:first_name]} #{params[:last_name]}"
908
- else
909
- 'John Doe'
910
- end
911
- end
1107
+ puts result2 # 0
1108
+ ```
912
1109
 
913
- #
914
- # You can also use Kind::Optional(<Type>) to achieve the same behavior
915
- #
1110
+ This typed maybe has the same methods of `Kind::Maybe` class. e.g:
1111
+
1112
+ ```ruby
1113
+ Kind::Maybe(Numeric)[5]
1114
+ Kind::Maybe(Numeric).new(5)
1115
+ Kind::Maybe(Numeric).wrap(5)
1116
+
1117
+ # ---
1118
+
1119
+ Kind::Optional(Numeric)[5]
1120
+ Kind::Optional(Numeric).new(5)
1121
+ Kind::Optional(Numeric).wrap(5)
1122
+ ```
1123
+
1124
+ #### Real world examples
1125
+
1126
+ It is very common the need to avoid some operation when a method receives the wrong input.
1127
+ In these scenarios, you could create a maybe monad that will return None if the given input hasn't the expected type. e.g:
1128
+
1129
+ ```ruby
916
1130
  def person_name(params)
917
- Kind::Optional(Hash)
1131
+ Kind::Maybe(Hash)
918
1132
  .wrap(params)
919
- .map { |data| data if data.values_at(:first_name, :last_name).compact.size == 2 }
920
- .map { |data| "#{data[:first_name]} #{data[:last_name]}" }
1133
+ .then { |hash| hash.values_at(:first_name, :last_name) }
1134
+ .then { |names| names.map(&Kind::Presence).tap(&:compact!) }
1135
+ .check { |names| names.size == 2 }
1136
+ .then { |(first_name, last_name)| "#{first_name} #{last_name}" }
921
1137
  .value_or { 'John Doe' }
922
1138
  end
923
- ```
924
1139
 
925
- > Note: You could use the `.as_optional` method (or it alias `.as_maybe`) with any [type checker](https://github.com/serradura/kind/blob/b177fed9cc2b3347d63963a2a2fd99f989c51a9a/README.md#type-checkers).
1140
+ person_name('') # "John Doe"
1141
+ person_name(nil) # "John Doe"
1142
+
1143
+ person_name(first_name: 'Rodrigo') # "John Doe"
1144
+ person_name(last_name: 'Serradura') # "John Doe"
926
1145
 
927
- Let's see another example using a collection and how the method `.as_optional` works when it receives no argument.
1146
+ person_name(first_name: 'Rodrigo', last_name: 'Serradura') # "Rodrigo Serradura"
928
1147
 
929
- ```ruby
930
- collection = [ {number: 1}, 'number 0', {number: 2}, [0] ]
1148
+ #
1149
+ # See below the previous implementation without using the maybe monad.
1150
+ #
1151
+ def person_name(params)
1152
+ default = 'John Doe'
931
1153
 
932
- collection
933
- .select(&Kind.of.Hash.as_optional)
934
- .reduce(0) do |total, item|
935
- item.try { |data| data[:number] + total } || total
936
- end
1154
+ return default unless params.kind_of?(Hash)
1155
+
1156
+ names = params.values_at(:first_name, :last_name).map(&Kind::Presence).tap(&:compact!)
937
1157
 
938
- collection
939
- .map(&Kind.of.Hash.as_optional).select(&:some?)
940
- .reduce(0) { |total, item| total + item.value[:number] }
1158
+ return default if names.size != 2
941
1159
 
942
- # Note: All the examples above return 3 as the sum of all hashes with numbers.
1160
+ first_name, last_name = names
1161
+
1162
+ "#{first_name} #{last_name}"
1163
+ end
943
1164
  ```
944
1165
 
945
- To finish follows an example of how to use optionals to handle arguments in coupled methods.
1166
+ To finish follows an example of how to use the Maybe monad to handle arguments in coupled methods.
946
1167
 
947
1168
  ```ruby
948
- module PersonIntroduction
1169
+ module PersonIntroduction1
949
1170
  extend self
950
1171
 
951
1172
  def call(params)
952
- optional = Kind::Of::Hash.as_optional(params)
1173
+ optional = Kind::Maybe(Hash).wrap(params)
953
1174
 
954
1175
  "Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old."
955
1176
  end
@@ -957,19 +1178,20 @@ module PersonIntroduction
957
1178
  private
958
1179
 
959
1180
  def full_name(optional)
960
- optional.map { |data| "#{data[:first_name]} #{data[:last_name]}" }
1181
+ optional.map { |hash| "#{hash[:first_name]} #{hash[:last_name]}".strip }
1182
+ .presence
961
1183
  .value_or { 'John Doe' }
962
1184
  end
963
1185
 
964
1186
  def age(optional)
965
- optional.map { |data| data[:age] }.value_or(0)
1187
+ optional.dig(:age).value_or(0)
966
1188
  end
967
1189
  end
968
1190
 
969
1191
  #
970
1192
  # See below the previous implementation without using an optional.
971
1193
  #
972
- module PersonIntroduction
1194
+ module PersonIntroduction2
973
1195
  extend self
974
1196
 
975
1197
  def call(params)
@@ -979,9 +1201,12 @@ module PersonIntroduction
979
1201
  private
980
1202
 
981
1203
  def full_name(params)
1204
+ default = 'John Doe'
1205
+
982
1206
  case params
983
- when Hash then "#{params[:first_name]} #{params[:last_name]}"
984
- else 'John Doe'
1207
+ when Hash then
1208
+ Kind::Presence.("#{params[:first_name]} #{params[:last_name]}".strip) || default
1209
+ else default
985
1210
  end
986
1211
  end
987
1212
 
@@ -994,47 +1219,49 @@ module PersonIntroduction
994
1219
  end
995
1220
  ```
996
1221
 
997
- [⬆️ Back to Top](#table-of-contents-)
1222
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
998
1223
 
999
- ### Kind::Maybe(<Type>)
1224
+ ### Error handling
1000
1225
 
1001
- There is an alternative to `Kind.of.\<Type\>.as_optional`, you can use `Kind::Optional(<Type>)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:
1226
+ #### Kind::Maybe.wrap {}
1227
+
1228
+ The `Kind::Maybe#wrap` can receive a block, and if an exception (at `StandardError level`) happening, this will generate a None result.
1002
1229
 
1003
1230
  ```ruby
1004
- result1 =
1005
- Kind::Maybe(Numeric)
1006
- .wrap(5)
1007
- .then { |value| value * 5 }
1008
- .value_or { 0 }
1231
+ Kind::Maybe(Numeric)
1232
+ .wrap { 2 / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
1009
1233
 
1010
- puts result1 # 25
1234
+ Kind::Maybe(Numeric)
1235
+ .wrap(2) { |number| number / 0 } # #<Kind::Maybe::None:0x0000... @value=#<ZeroDivisionError: divided by 0>>
1236
+ ```
1011
1237
 
1012
- # ---
1238
+ #### Kind::Maybe.map! or Kind::Maybe.then!
1013
1239
 
1014
- result2 =
1015
- Kind::Optional(Numeric)
1016
- .wrap('5')
1017
- .then { |value| value * 5 }
1018
- .value_or { 0 }
1240
+ By default the `Kind::Maybe#map` and `Kind::Maybe#then` intercept exceptions at the `StandardError` level. So if an exception was intercepted a None will be returned.
1019
1241
 
1020
- puts result2 # 0
1242
+ ```ruby
1243
+ # Handling StandardError exceptions
1244
+ result1 = Kind::Maybe[2].map { |number| number / 0 }
1245
+ result1.none? # true
1246
+ result1.value # #<ZeroDivisionError: divided by 0>
1247
+
1248
+ result2 = Kind::Maybe[3].then { |number| number / 0 }
1249
+ result2.none? # true
1250
+ result2.value # #<ZeroDivisionError: divided by 0>
1021
1251
  ```
1022
1252
 
1023
- This typed maybe has the same methods of `Kind::Maybe` class. e.g:
1253
+ But there are versions of these methods (`Kind::Maybe#map!` and `Kind::Maybe#then!`) that allow the exception leak, so, the user must handle the exception by himself or use this method when he wants to see the error be raised.
1024
1254
 
1025
1255
  ```ruby
1026
- Kind::Maybe(Numeric)[5]
1027
- Kind::Maybe(Numeric).new(5)
1028
- Kind::Maybe(Numeric).wrap(5)
1029
-
1030
- # ---
1256
+ # Leaking StandardError exceptions
1257
+ Kind::Maybe[2].map! { |number| number / 0 } # ZeroDivisionError (divided by 0)
1031
1258
 
1032
- Kind::Optional(Numeric)[5]
1033
- Kind::Optional(Numeric).new(5)
1034
- Kind::Optional(Numeric).wrap(5)
1259
+ Kind::Maybe[2].then! { |number| number / 0 } # ZeroDivisionError (divided by 0)
1035
1260
  ```
1036
1261
 
1037
- [⬆️ Back to Top](#table-of-contents-)
1262
+ > **Note:** If an exception (at StandardError level) is returned by the methods `#then`, `#map` it will be resolved as None.
1263
+
1264
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1038
1265
 
1039
1266
  ### Kind::Maybe#try
1040
1267
 
@@ -1074,7 +1301,7 @@ Kind::Maybe[object].try { |value| value.upcase }.value # nil
1074
1301
 
1075
1302
  > **Note:** You can use the `#try` method with `Kind::Optional` objects.
1076
1303
 
1077
- [⬆️ Back to Top](#table-of-contents-)
1304
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1078
1305
 
1079
1306
  ### Kind::Maybe#try!
1080
1307
 
@@ -1088,7 +1315,96 @@ Kind::Maybe[{}].try!(:upcase) # => NoMethodError (undefined method `upcase' for
1088
1315
 
1089
1316
  > **Note:** You can also use the `#try!` method with `Kind::Optional` objects.
1090
1317
 
1091
- [⬆️ Back to Top](#table-of-contents-)
1318
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1319
+
1320
+ ### Kind::Maybe#dig
1321
+
1322
+ Has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some value can't be digged.
1323
+
1324
+ ```ruby
1325
+ [nil, 1, '', /x/].each do |value|
1326
+ p Kind::Maybe[value].dig(:foo).value # nil
1327
+ end
1328
+
1329
+ # --
1330
+
1331
+ a = [1, 2, 3]
1332
+
1333
+ Kind::Maybe[a].dig(0).value # 1
1334
+
1335
+ Kind::Maybe[a].dig(3).value # nil
1336
+
1337
+ # --
1338
+
1339
+ h = { foo: {bar: {baz: 1}}}
1340
+
1341
+ Kind::Maybe[h].dig(:foo).value # {bar: {baz: 1}}
1342
+ Kind::Maybe[h].dig(:foo, :bar).value # {baz: 1}
1343
+ Kind::Maybe[h].dig(:foo, :bar, :baz).value # 1
1344
+
1345
+ Kind::Maybe[h].dig(:foo, :bar, 'baz').value # nil
1346
+
1347
+ # --
1348
+
1349
+ i = { foo: [{'bar' => [1, 2]}, {baz: [3, 4]}] }
1350
+
1351
+ Kind::Maybe[i].dig(:foo, 0, 'bar', 0).value # 1
1352
+ Kind::Maybe[i].dig(:foo, 0, 'bar', 1).value # 2
1353
+ Kind::Maybe[i].dig(:foo, 0, 'bar', -1).value # 2
1354
+
1355
+ Kind::Maybe[i].dig(:foo, 0, 'bar', 2).value # nil
1356
+
1357
+ # --
1358
+
1359
+ s = Struct.new(:a, :b).new(101, 102)
1360
+ o = OpenStruct.new(c: 103, d: 104)
1361
+ b = { struct: s, ostruct: o, data: [s, o]}
1362
+
1363
+ Kind::Maybe[s].dig(:a).value # 101
1364
+ Kind::Maybe[b].dig(:struct, :b).value # 102
1365
+ Kind::Maybe[b].dig(:data, 0, :b).value # 102
1366
+ Kind::Maybe[b].dig(:data, 0, 'b').value # 102
1367
+
1368
+ Kind::Maybe[o].dig(:c).value # 103
1369
+ Kind::Maybe[b].dig(:ostruct, :d).value # 104
1370
+ Kind::Maybe[b].dig(:data, 1, :d).value # 104
1371
+ Kind::Maybe[b].dig(:data, 1, 'd').value # 104
1372
+
1373
+ Kind::Maybe[s].dig(:f).value # nil
1374
+ Kind::Maybe[o].dig(:f).value # nil
1375
+ Kind::Maybe[b].dig(:struct, :f).value # nil
1376
+ Kind::Maybe[b].dig(:ostruct, :f).value # nil
1377
+ Kind::Maybe[b].dig(:data, 0, :f).value # nil
1378
+ Kind::Maybe[b].dig(:data, 1, :f).value # nil
1379
+ ```
1380
+
1381
+ > **Note:** You can also use the `#dig` method with `Kind::Optional` objects.
1382
+
1383
+ ### Kind::Maybe#check
1384
+
1385
+ This method will return the current Some after verify if the block output is truthy. e.g:
1386
+
1387
+ ```ruby
1388
+ Kind::Maybe(Array)
1389
+ .wrap(['Rodrigo', 'Serradura'])
1390
+ .then { |names| names.map(&Kind::Presence).tap(&:compact!) }
1391
+ .check { |names| names.size == 2 }
1392
+ .value # ["Rodrigo", "Serradura"]
1393
+ ```
1394
+
1395
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1396
+
1397
+ ### Kind::Maybe#presence
1398
+
1399
+ This method will return None if the wrapped value wasn't present.
1400
+
1401
+ ```ruby
1402
+ result = Kind::Maybe(Hash).wrap(foo: '').dig(:foo).presence
1403
+ result.none? # true
1404
+ result.value # nil
1405
+ ```
1406
+
1407
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1092
1408
 
1093
1409
  ## Kind::Empty
1094
1410
 
@@ -1120,7 +1436,11 @@ def do_something(value, with_options: Kind::Empty::HASH)
1120
1436
  end
1121
1437
  ```
1122
1438
 
1123
- One last thing, if there is no constant declared as Empty, the `kind` gem will define `Empty` as an alias for `Kind::Empty`. Knowing this, the previous example could be written like this:
1439
+ ### Defining Empty as Kind::Empty an alias
1440
+
1441
+ You can require `kind/empty/constant` to define `Empty` as a `Kind::Empty` alias. But, a `LoadError` will be raised if there is an already defined constant `Empty`.
1442
+
1443
+ So if you required this file, the previous example could be written like this:
1124
1444
 
1125
1445
  ```ruby
1126
1446
  def do_something(value, with_options: Empty::HASH)
@@ -1128,14 +1448,194 @@ def do_something(value, with_options: Empty::HASH)
1128
1448
  end
1129
1449
  ```
1130
1450
 
1131
- Follows the list of constants, if the alias is available to be created:
1451
+ Follows the list of constants if the alias was defined:
1132
1452
 
1133
1453
  - `Empty::SET`
1134
1454
  - `Empty::HASH`
1135
1455
  - `Empty::ARRAY`
1136
1456
  - `Empty::STRING`
1137
1457
 
1138
- [⬆️ Back to Top](#table-of-contents-)
1458
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1459
+
1460
+ ## Kind::Validator (ActiveModel::Validations)
1461
+
1462
+ This module enables the capability to validate types via [`ActiveModel::Validations >= 3.2, < 6.1.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g
1463
+
1464
+ ```ruby
1465
+ class Person
1466
+ include ActiveModel::Validations
1467
+
1468
+ attr_accessor :first_name, :last_name
1469
+
1470
+ validates :first_name, :last_name, kind: String
1471
+ end
1472
+ ```
1473
+
1474
+ And to make use of it, you will need to do an explicitly require. e.g:
1475
+
1476
+ ```ruby
1477
+ # In some Gemfile
1478
+ gem 'kind', require: 'kind/validator'
1479
+
1480
+ # In some .rb file
1481
+ require 'kind/validator'
1482
+ ```
1483
+
1484
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1485
+
1486
+ ### Usage
1487
+
1488
+ #### [Object#===](https://ruby-doc.org/core-3.0.0/Object.html#method-i-3D-3D-3D)
1489
+
1490
+ ```ruby
1491
+ validates :name, kind: { of: String }
1492
+ ```
1493
+
1494
+ Use an array to verify if the attribute is an instance of one of the classes/modules.
1495
+
1496
+ ```ruby
1497
+ validates :status, kind: { of: [String, Symbol]}
1498
+ ```
1499
+
1500
+ Because of kind verification be made via `===` you can use type checkers as the expected kinds.
1501
+
1502
+ ```ruby
1503
+ validates :alive, kind: Kind::Boolean
1504
+ ```
1505
+
1506
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1507
+
1508
+ #### [Kind.is](#verifying-the-kind-of-some-classmodule)
1509
+
1510
+ ```ruby
1511
+ #
1512
+ # Verifying if the attribute value is the class or a subclass.
1513
+ #
1514
+ class Human; end
1515
+ class Person < Human; end
1516
+ class User < Human; end
1517
+
1518
+ validates :human_kind, kind: { is: Human }
1519
+
1520
+ #
1521
+ # Verifying if the attribute value is the module or if it is a class that includes the module
1522
+ #
1523
+ module Human; end
1524
+ class Person; include Human; end
1525
+ class User; include Human; end
1526
+
1527
+ validates :human_kind, kind: { is: Human }
1528
+
1529
+ #
1530
+ # Verifying if the attribute value is the module or if it is a module that extends the module
1531
+ #
1532
+ module Human; end
1533
+ module Person; extend Human; end
1534
+ module User; extend Human; end
1535
+
1536
+ validates :human_kind, kind: { is: Human }
1537
+
1538
+ # or use an array to verify if the attribute
1539
+ # is a kind of one those classes/modules.
1540
+
1541
+ validates :human_kind, kind: { is: [Person, User] }
1542
+ ```
1543
+
1544
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1545
+
1546
+ #### [Object#instance_of?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-instance_of-3F)
1547
+
1548
+ ```ruby
1549
+ validates :name, kind: { instance_of: String }
1550
+
1551
+ # or use an array to verify if the attribute
1552
+ # is an instance of one of the classes/modules.
1553
+
1554
+ validates :name, kind: { instance_of: [String, Symbol] }
1555
+ ```
1556
+
1557
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1558
+
1559
+ #### [Object#respond_to?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-respond_to-3F)
1560
+
1561
+ ```ruby
1562
+ validates :handler, kind: { respond_to: :call }
1563
+ ```
1564
+
1565
+ This validation can verify one or multiple methods.
1566
+
1567
+ ```ruby
1568
+ validates :params, kind: { respond_to: [:[], :values_at] }
1569
+ ```
1570
+
1571
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1572
+
1573
+ #### Array.new.all? { |item| item.kind_of?(Class) }
1574
+
1575
+ ```ruby
1576
+ validates :account_types, kind: { array_of: String }
1577
+
1578
+ # or use an array to verify if the attribute
1579
+ # is an instance of one of the classes
1580
+
1581
+ validates :account_types, kind: { array_of: [String, Symbol] }
1582
+ ```
1583
+
1584
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1585
+
1586
+ #### Array.new.all? { |item| expected_values.include?(item) }
1587
+
1588
+ ```ruby
1589
+ # Verifies if the attribute value
1590
+ # is an array with some or all the expected values.
1591
+
1592
+ validates :account_types, kind: { array_with: ['foo', 'bar'] }
1593
+ ```
1594
+
1595
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1596
+
1597
+ ### Defining the default validation strategy
1598
+
1599
+ By default, you can define the attribute type directly (without a hash). e.g.
1600
+
1601
+ ```ruby
1602
+ validates :name, kind: String
1603
+ # or
1604
+ validates :name, kind: [String, Symbol]
1605
+ ```
1606
+
1607
+ To changes this behavior you can set another strategy to validates the attributes types:
1608
+
1609
+ ```ruby
1610
+ Kind::Validator.default_strategy = :instance_of
1611
+
1612
+ # Tip: Create an initializer if you are in a Rails application.
1613
+ ```
1614
+
1615
+ And these are the available options to define the default strategy:
1616
+ - `kind_of` *(default)*
1617
+ - `instance_of`
1618
+
1619
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1620
+
1621
+ ### Using the `allow_nil` and `strict` options
1622
+
1623
+ You can use the `allow_nil` option with any of the kind validations. e.g.
1624
+
1625
+ ```ruby
1626
+ validates :name, kind: String, allow_nil: true
1627
+ ```
1628
+
1629
+ And as any active model validation, kind validations works with the `strict: true`
1630
+ option and with the `validates!` method. e.g.
1631
+
1632
+ ```ruby
1633
+ validates :first_name, kind: String, strict: true
1634
+ # or
1635
+ validates! :last_name, kind: String
1636
+ ```
1637
+
1638
+ [⬆️ &nbsp;Back to Top](#table-of-contents-)
1139
1639
 
1140
1640
  ## Similar Projects
1141
1641