kind 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d3ff3a2a9d5b61087a96cf0255208086b5338f2a5f5ccb4ca609a9c8cff01e5
4
- data.tar.gz: 10d77db1c01fb6990056fbb1b3a2dde463b8627af1f5aa415b7b11a89b76c367
3
+ metadata.gz: 0ebe119486837b90cdb9bd3c61a7d429d218608c0a84b19c94dd9d752909b194
4
+ data.tar.gz: b881aa36c0c9611eaa755f1cf99a2cff3c320975450a8136eef244de612ba755
5
5
  SHA512:
6
- metadata.gz: ef9bd5cfb51763cb15690e232e60b474c6f958f5d2de7faab9d5688ce1f9e9b74c529b2bc2fbae39082e7bc0ea13b5d95038d6fd488f0a8d96d576e4843eb5c4
7
- data.tar.gz: 515db015436abfe14cc392d46cdb41e8590af11c1d5e8e12466a4d85bfcd8681759e40f20ffbd9bac44a859a9b658e37c9218f2eec9bcc9118f68d3f755b8c56
6
+ metadata.gz: 27fd2b57f56612086698768b584e5d26641ad2f568d023b0ef78a7ac12ea0333dfeadf05f2af69d496448e04b2c396782a3a95f17a93cf92b995ce3d5677a986
7
+ data.tar.gz: 14a9f90efd784f353cab0fd174b6bc6d715fc0905ff79d38c692eeacf41e43fac2404b8c40f8381e4e201f9bba14c529196c192aa3acec411b7e163b1864077d
data/README.md CHANGED
@@ -26,6 +26,10 @@ One of the goals of this project is to do simple type checking like `"some strin
26
26
  - [Special type checkers](#special-type-checkers)
27
27
  - [Kind.of](#kindof)
28
28
  - [Kind.is](#kindis)
29
+ - [Kind::Undefined](#kindundefined)
30
+ - [Kind::Optional](#kindoptional)
31
+ - [Kind::Optional[] and Kind::Optional#then](#kindoptional-and-kindoptionalthen)
32
+ - [Kind::Optional#try](#kindoptionaltry)
29
33
  - [Development](#development)
30
34
  - [Contributing](#contributing)
31
35
  - [License](#license)
@@ -274,6 +278,103 @@ The list of types (classes and modules) available to use with `Kind.of.*` or `Ki
274
278
 
275
279
  [⬆️ Back to Top](#table-of-contents-)
276
280
 
281
+ ## Kind::Undefined
282
+
283
+ The [`Kind::Undefined`](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind/undefined.rb) constant is used as the default argument of type checkers. This is necessary [to know if no arguments were passed to the type check methods](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/lib/kind.rb#L45-L48). But, you can use it in your codebase too, especially if you need to distinguish the usage of `nil` as a method argument.
284
+
285
+ If you are interested, check out [the tests](https://github.com/serradura/kind/blob/834f6b8ebdc737de8e5628986585f30c1a5aa41b/test/kind/undefined_test.rb) to understand its methods.
286
+
287
+ [⬆️ Back to Top](#table-of-contents-)
288
+
289
+ ## Kind::Optional
290
+
291
+ The `Kind::Optional` is used when a series of computations (in a chain of map callings) could return `nil` or `Kind::Undefined` at any point.
292
+
293
+ ```ruby
294
+ optional =
295
+ Kind::Optional.new(2)
296
+ .map { |value| value * 2 }
297
+ .map { |value| value * 2 }
298
+
299
+ puts optional.value # 8
300
+ puts optional.some? # true
301
+ puts optional.none? # false
302
+ puts optional.value_or(0) # 8
303
+ puts optional.value_or { 0 } # 8
304
+
305
+ #################
306
+ # Returning nil #
307
+ #################
308
+
309
+ optional =
310
+ Kind::Optional.new(3)
311
+ .map { nil }
312
+ .map { |value| value * 3 }
313
+
314
+ puts optional.value # nil
315
+ puts optional.some? # false
316
+ puts optional.none? # true
317
+ puts optional.value_or(0) # 0
318
+ puts optional.value_or { 0 } # 0
319
+
320
+ #############################
321
+ # Returning Kind::Undefined #
322
+ #############################
323
+
324
+ optional =
325
+ Kind::Optional.new(4)
326
+ .map { Kind::Undefined }
327
+ .map { |value| value * 4 }
328
+
329
+ puts optional.value # Kind::Undefined
330
+ puts optional.some? # false
331
+ puts optional.none? # true
332
+ puts optional.value_or(1) # 1
333
+ puts optional.value_or { 1 } # 1
334
+ ```
335
+
336
+ ### Kind::Optional[] and Kind::Optional#then
337
+
338
+ You can use `Kind::Option[]` (brackets) instead of the `.new` to transform values in a `Kind::Optional`. Another alias is `.then` to the `.map` method.
339
+
340
+ ```ruby
341
+ result =
342
+ Kind::Optional[5]
343
+ .then { |value| value * 5 }
344
+ .then { |value| value + 17 }
345
+ .value_or(0)
346
+
347
+ puts result # 42
348
+ ```
349
+
350
+ ### Kind::Optional#try
351
+
352
+ If you don't want to use a map to access the value, you could use the `#try` method to access it. So, if the value wasn't `nil` or `Kind::Undefined`, it will be returned.
353
+
354
+ ```ruby
355
+ p Kind::Optional['foo'].try(:upcase) # "FOO"
356
+
357
+ p Kind::Optional['bar'].try { |value| value.upcase } # "BAR"
358
+
359
+ #############
360
+ # Nil value #
361
+ #############
362
+
363
+ p Kind::Optional[nil].try(:upcase) # nil
364
+
365
+ p Kind::Optional[nil].try { |value| value.upcase } # nil
366
+
367
+ #########################
368
+ # Kind::Undefined value #
369
+ #########################
370
+
371
+ p Kind::Optional[Kind::Undefined].try(:upcase) # nil
372
+
373
+ p Kind::Optional[Kind::Undefined].try { |value| value.upcase } # nil
374
+ ```
375
+
376
+ [⬆️ Back to Top](#table-of-contents-)
377
+
277
378
  ## Development
278
379
 
279
380
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Checker
5
+ def class?(value)
6
+ Kind::Is.(__kind, value)
7
+ end
8
+
9
+ def instance?(value)
10
+ value.is_a?(__kind)
11
+ end
12
+
13
+ def or_nil(value)
14
+ return value if instance?(value)
15
+ end
16
+ end
17
+
18
+ private_constant :Checker
19
+ end
data/lib/kind/error.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Error < TypeError
5
+ def initialize(klass, object)
6
+ super("#{object.inspect} expected to be a kind of #{klass}")
7
+ end
8
+ end
9
+ end
data/lib/kind/is.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Is
5
+ def self.call(expected, value)
6
+ expected_mod = Kind::Of.Module(expected)
7
+ mod = Kind::Of.Module(value)
8
+
9
+ mod <= expected_mod || false
10
+ end
11
+
12
+ def self.Class(value)
13
+ value.is_a?(::Class)
14
+ end
15
+
16
+ def self.Module(value)
17
+ value == ::Module || (value.is_a?(::Module) && !self.Class(value))
18
+ end
19
+ end
20
+ end
data/lib/kind/of.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Of
5
+ def self.call(klass, object, name: nil)
6
+ return object if object.is_a?(klass)
7
+
8
+ raise Kind::Error.new((name || klass.name), object)
9
+ end
10
+
11
+ def self.Class(object = Undefined)
12
+ return Class if object == Undefined
13
+
14
+ self.call(::Class, object)
15
+ end
16
+
17
+ const_set(:Class, ::Module.new do
18
+ extend Checker
19
+
20
+ def self.__kind; ::Class; end
21
+
22
+ def self.class?(value); Kind::Is.Class(value); end
23
+
24
+ def self.instance?(value); class?(value); end
25
+ end)
26
+
27
+ def self.Module(object = Undefined)
28
+ return Module if object == Undefined
29
+
30
+ self.call(::Module, object)
31
+ end
32
+
33
+ const_set(:Module, ::Module.new do
34
+ extend Checker
35
+
36
+ def self.__kind; ::Module; end
37
+
38
+ def self.class?(value); Kind::Is.Module(value); end
39
+
40
+ def self.instance?(value); class?(value); end
41
+ end)
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Optional
5
+ self.singleton_class.send(:alias_method, :[], :new)
6
+
7
+ IsNone = -> value { value == nil || value == Undefined }
8
+
9
+ attr_reader :value
10
+
11
+ def initialize(arg)
12
+ @value = arg.is_a?(Kind::Optional) ? arg.value : arg
13
+ end
14
+
15
+ INVALID_DEFAULT_ARG = 'the default value must be defined as an argument or block'.freeze
16
+
17
+ def value_or(default = Undefined, &block)
18
+ return @value if some?
19
+
20
+ if default == Undefined && !block
21
+ raise ArgumentError, INVALID_DEFAULT_ARG
22
+ else
23
+ IsNone.(default) ? block.call : default
24
+ end
25
+ end
26
+
27
+ def none?
28
+ @none ||= IsNone.(@value)
29
+ end
30
+
31
+ def some?
32
+ !none?
33
+ end
34
+
35
+ def map(&fn)
36
+ return self if none?
37
+
38
+ self.class.new(fn.call(@value))
39
+ end
40
+
41
+ alias_method :then, :map
42
+
43
+ def try(method_name = Undefined, &block)
44
+ fn = method_name == Undefined ? block : Kind.of.Symbol(method_name).to_proc
45
+
46
+ unless IsNone.(value)
47
+ result = fn.call(value)
48
+
49
+ return result unless IsNone.(result)
50
+ end
51
+ end
52
+
53
+ private_constant :IsNone, :INVALID_DEFAULT_ARG
54
+ end
55
+ end
data/lib/kind/types.rb ADDED
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Types
5
+ extend self
6
+
7
+ COLONS = '::'.freeze
8
+
9
+ KIND_OF = <<-RUBY
10
+ def self.%{method_name}(object = Undefined, options = {})
11
+ default = options[:or]
12
+
13
+ return Kind::Of::%{kind_name} if object == Undefined && default.nil?
14
+
15
+ Kind::Of.(::%{kind_name}, (object || default), name: "%{kind_name}".freeze)
16
+ end
17
+ RUBY
18
+
19
+ KIND_IS = <<-RUBY
20
+ def self.%{method_name}(value = Undefined)
21
+ return Kind::Is::%{kind_name} if value == Undefined
22
+
23
+ Kind::Is.(::%{kind_name}, value)
24
+ end
25
+ RUBY
26
+
27
+ private_constant :KIND_OF, :KIND_IS, :COLONS
28
+
29
+ def add(kind, name: nil)
30
+ kind_name = Kind.of.Module(kind).name
31
+ checker = name ? Kind::Of.(String, name) : kind_name
32
+
33
+ case
34
+ when checker.include?(COLONS)
35
+ add_kind_with_namespace(checker, method_name: name)
36
+ else
37
+ add_root(checker, kind_name, method_name: name, create_kind_is_mod: false)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def add_root(checker, kind_name, method_name:, create_kind_is_mod:)
44
+ root_checker = method_name ? method_name : checker
45
+ root_kind_name = method_name ? method_name : kind_name
46
+
47
+ add_kind(root_checker, root_kind_name, Kind::Of, Kind::Is, create_kind_is_mod)
48
+ end
49
+
50
+ def add_kind_with_namespace(checker, method_name:)
51
+ raise NotImplementedError if method_name
52
+
53
+ const_names = checker.split(COLONS)
54
+ const_names.each_with_index do |const_name, index|
55
+ if index == 0
56
+ add_root(const_name, const_name, method_name: nil, create_kind_is_mod: true)
57
+ else
58
+ add_node(const_names, index)
59
+ end
60
+ end
61
+ end
62
+
63
+ def add_node(const_names, index)
64
+ namespace = const_names[0..(index-1)]
65
+ namespace_name = namespace.join(COLONS)
66
+
67
+ kind_of_mod = Kind::Of.const_get(namespace_name)
68
+ kind_is_mod = Kind::Is.const_get(namespace_name)
69
+
70
+ checker = const_names[index]
71
+ kind_name = const_names[0..index].join(COLONS)
72
+
73
+ add_kind(checker, kind_name, kind_of_mod, kind_is_mod, true)
74
+ end
75
+
76
+ def add_kind(checker, kind_name, kind_of_mod, kind_is_mod, create_kind_is_mod)
77
+ params = { method_name: checker, kind_name: kind_name }
78
+
79
+ unless kind_of_mod.respond_to?(checker)
80
+ kind_checker = ::Module.new { extend Checker }
81
+ kind_checker.module_eval("def self.__kind; #{kind_name}; end")
82
+
83
+ kind_of_mod.instance_eval(KIND_OF % params)
84
+ kind_of_mod.const_set(checker, kind_checker)
85
+ end
86
+
87
+ unless kind_is_mod.respond_to?(checker)
88
+ kind_is_mod.instance_eval(KIND_IS % params)
89
+ kind_is_mod.const_set(checker, Module.new) if create_kind_is_mod
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ Undefined = Object.new.tap do |undefined|
5
+ def undefined.inspect
6
+ @inspect ||= 'Undefined'.freeze
7
+ end
8
+
9
+ def undefined.to_s
10
+ inspect
11
+ end
12
+
13
+ def undefined.clone
14
+ self
15
+ end
16
+
17
+ def undefined.dup
18
+ clone
19
+ end
20
+
21
+ def undefined.default(value, default)
22
+ return self if value != self
23
+
24
+ default.respond_to?(:call) ? default.call : default
25
+ end
26
+ end
27
+ end
data/lib/kind/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kind
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/kind.rb CHANGED
@@ -1,177 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'kind/version'
4
+ require 'kind/undefined'
5
+ require 'kind/optional'
6
+ require 'kind/error'
7
+ require 'kind/is'
8
+ require 'kind/checker'
9
+ require 'kind/of'
10
+ require 'kind/types'
4
11
 
5
12
  module Kind
6
- Undefined = Object.new
7
-
8
- class Error < TypeError
9
- def initialize(klass, object)
10
- super("#{object.inspect} expected to be a kind of #{klass}")
11
- end
12
- end
13
-
14
- module Is
15
- def self.call(expected, value)
16
- expected_mod = Kind::Of.Module(expected)
17
- mod = Kind::Of.Module(value)
18
-
19
- mod <= expected_mod || false
20
- end
21
-
22
- def self.Class(value)
23
- value.is_a?(::Class)
24
- end
25
-
26
- def self.Module(value)
27
- value == ::Module || (value.is_a?(::Module) && !self.Class(value))
28
- end
29
- end
30
-
31
- module Checker
32
- def class?(value)
33
- Kind::Is.(__kind, value)
34
- end
35
-
36
- def instance?(value)
37
- value.is_a?(__kind)
38
- end
39
-
40
- def or_nil(value)
41
- return value if instance?(value)
42
- end
43
- end
44
-
45
- module Of
46
- def self.call(klass, object, name: nil)
47
- return object if object.is_a?(klass)
48
-
49
- raise Kind::Error.new((name || klass.name), object)
50
- end
51
-
52
- def self.Class(object = Undefined)
53
- return Class if object == Undefined
54
-
55
- self.call(::Class, object)
56
- end
57
-
58
- const_set(:Class, ::Module.new do
59
- extend Checker
60
-
61
- def self.__kind; ::Class; end
62
-
63
- def self.class?(value); Kind::Is.Class(value); end
64
-
65
- def self.instance?(value); class?(value); end
66
- end)
67
-
68
- def self.Module(object = Undefined)
69
- return Module if object == Undefined
70
-
71
- self.call(::Module, object)
72
- end
73
-
74
- const_set(:Module, ::Module.new do
75
- extend Checker
76
-
77
- def self.__kind; ::Module; end
78
-
79
- def self.class?(value); Kind::Is.Module(value); end
80
-
81
- def self.instance?(value); class?(value); end
82
- end)
83
- end
84
-
85
- module Types
86
- extend self
87
-
88
- COLONS = '::'.freeze
89
-
90
- KIND_OF = <<-RUBY
91
- def self.%{method_name}(object = Undefined, options = {})
92
- default = options[:or]
93
-
94
- return Kind::Of::%{kind_name} if object == Undefined && default.nil?
95
-
96
- Kind::Of.(::%{kind_name}, (object || default), name: "%{kind_name}".freeze)
97
- end
98
- RUBY
99
-
100
- KIND_IS = <<-RUBY
101
- def self.%{method_name}(value = Undefined)
102
- return Kind::Is::%{kind_name} if value == Undefined
103
-
104
- Kind::Is.(::%{kind_name}, value)
105
- end
106
- RUBY
107
-
108
- private_constant :KIND_OF, :KIND_IS, :COLONS
109
-
110
- def add(kind, name: nil)
111
- kind_name = Kind.of.Module(kind).name
112
- checker = name ? Kind::Of.(String, name) : kind_name
113
-
114
- case
115
- when checker.include?(COLONS)
116
- add_kind_with_namespace(checker, method_name: name)
117
- else
118
- add_root(checker, kind_name, method_name: name, create_kind_is_mod: false)
119
- end
120
- end
121
-
122
- private
123
-
124
- def add_root(checker, kind_name, method_name:, create_kind_is_mod:)
125
- root_checker = method_name ? method_name : checker
126
- root_kind_name = method_name ? method_name : kind_name
127
-
128
- add_kind(root_checker, root_kind_name, Kind::Of, Kind::Is, create_kind_is_mod)
129
- end
130
-
131
- def add_kind_with_namespace(checker, method_name:)
132
- raise NotImplementedError if method_name
133
-
134
- const_names = checker.split(COLONS)
135
- const_names.each_with_index do |const_name, index|
136
- if index == 0
137
- add_root(const_name, const_name, method_name: nil, create_kind_is_mod: true)
138
- else
139
- add_node(const_names, index)
140
- end
141
- end
142
- end
143
-
144
- def add_node(const_names, index)
145
- namespace = const_names[0..(index-1)]
146
- namespace_name = namespace.join(COLONS)
147
-
148
- kind_of_mod = Kind::Of.const_get(namespace_name)
149
- kind_is_mod = Kind::Is.const_get(namespace_name)
150
-
151
- checker = const_names[index]
152
- kind_name = const_names[0..index].join(COLONS)
153
-
154
- add_kind(checker, kind_name, kind_of_mod, kind_is_mod, true)
155
- end
156
-
157
- def add_kind(checker, kind_name, kind_of_mod, kind_is_mod, create_kind_is_mod)
158
- params = { method_name: checker, kind_name: kind_name }
159
-
160
- unless kind_of_mod.respond_to?(checker)
161
- kind_checker = ::Module.new { extend Checker }
162
- kind_checker.module_eval("def self.__kind; #{kind_name}; end")
163
-
164
- kind_of_mod.instance_eval(KIND_OF % params)
165
- kind_of_mod.const_set(checker, kind_checker)
166
- end
167
-
168
- unless kind_is_mod.respond_to?(checker)
169
- kind_is_mod.instance_eval(KIND_IS % params)
170
- kind_is_mod.const_set(checker, Module.new) if create_kind_is_mod
171
- end
172
- end
173
- end
174
-
175
13
  def self.is; Is; end
176
14
  def self.of; Of; end
177
15
 
@@ -252,6 +90,4 @@ module Kind
252
90
  end
253
91
  end)
254
92
  end
255
-
256
- private_constant :Checker
257
93
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kind
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-09 00:00:00.000000000 Z
11
+ date: 2020-04-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Basic type system for Ruby (free of dependencies).
14
14
  email:
@@ -28,6 +28,13 @@ files:
28
28
  - bin/setup
29
29
  - kind.gemspec
30
30
  - lib/kind.rb
31
+ - lib/kind/checker.rb
32
+ - lib/kind/error.rb
33
+ - lib/kind/is.rb
34
+ - lib/kind/of.rb
35
+ - lib/kind/optional.rb
36
+ - lib/kind/types.rb
37
+ - lib/kind/undefined.rb
31
38
  - lib/kind/version.rb
32
39
  homepage: https://github.com/serradura/kind
33
40
  licenses: