kind 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: