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