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