kind 5.0.0 → 5.4.1

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/.travis.sh +39 -7
  4. data/.travis.yml +1 -0
  5. data/CHANGELOG.md +506 -28
  6. data/Gemfile +13 -6
  7. data/README.md +50 -42
  8. data/kind.gemspec +3 -3
  9. data/lib/kind.rb +2 -60
  10. data/lib/kind/__lib__/action_steps.rb +57 -0
  11. data/lib/kind/__lib__/attributes.rb +66 -0
  12. data/lib/kind/__lib__/kind.rb +51 -0
  13. data/lib/kind/__lib__/of.rb +17 -0
  14. data/lib/kind/__lib__/strict.rb +49 -0
  15. data/lib/kind/__lib__/undefined.rb +14 -0
  16. data/lib/kind/action.rb +127 -0
  17. data/lib/kind/active_model/validation.rb +3 -4
  18. data/lib/kind/basic.rb +79 -0
  19. data/lib/kind/basic/error.rb +29 -0
  20. data/lib/kind/{undefined.rb → basic/undefined.rb} +6 -1
  21. data/lib/kind/dig.rb +21 -5
  22. data/lib/kind/either.rb +30 -0
  23. data/lib/kind/either/left.rb +29 -0
  24. data/lib/kind/either/methods.rb +17 -0
  25. data/lib/kind/either/monad.rb +65 -0
  26. data/lib/kind/either/monad/wrapper.rb +19 -0
  27. data/lib/kind/either/right.rb +38 -0
  28. data/lib/kind/empty.rb +2 -0
  29. data/lib/kind/enum.rb +63 -0
  30. data/lib/kind/enum/item.rb +40 -0
  31. data/lib/kind/enum/methods.rb +72 -0
  32. data/lib/kind/function.rb +45 -0
  33. data/lib/kind/functional.rb +89 -0
  34. data/lib/kind/functional/action.rb +87 -0
  35. data/lib/kind/functional/steps.rb +22 -0
  36. data/lib/kind/immutable_attributes.rb +34 -0
  37. data/lib/kind/immutable_attributes/initializer.rb +70 -0
  38. data/lib/kind/immutable_attributes/reader.rb +38 -0
  39. data/lib/kind/maybe.rb +37 -12
  40. data/lib/kind/maybe/methods.rb +21 -0
  41. data/lib/kind/maybe/monad.rb +82 -0
  42. data/lib/kind/maybe/monad/wrapper.rb +19 -0
  43. data/lib/kind/maybe/none.rb +12 -19
  44. data/lib/kind/maybe/some.rb +68 -26
  45. data/lib/kind/maybe/typed.rb +11 -5
  46. data/lib/kind/maybe/{wrappable.rb → wrapper.rb} +8 -4
  47. data/lib/kind/monad.rb +22 -0
  48. data/lib/kind/monads.rb +5 -0
  49. data/lib/kind/objects.rb +17 -0
  50. data/lib/kind/objects/basic_object.rb +43 -0
  51. data/lib/kind/objects/modules.rb +32 -0
  52. data/lib/kind/{type_checkers → objects/modules}/core/array.rb +3 -1
  53. data/lib/kind/{type_checkers → objects/modules}/core/class.rb +1 -1
  54. data/lib/kind/{type_checkers → objects/modules}/core/comparable.rb +1 -1
  55. data/lib/kind/{type_checkers → objects/modules}/core/enumerable.rb +1 -1
  56. data/lib/kind/{type_checkers → objects/modules}/core/enumerator.rb +1 -1
  57. data/lib/kind/{type_checkers → objects/modules}/core/file.rb +1 -1
  58. data/lib/kind/{type_checkers → objects/modules}/core/float.rb +1 -1
  59. data/lib/kind/{type_checkers → objects/modules}/core/hash.rb +3 -1
  60. data/lib/kind/{type_checkers → objects/modules}/core/integer.rb +1 -1
  61. data/lib/kind/{type_checkers → objects/modules}/core/io.rb +1 -1
  62. data/lib/kind/{type_checkers → objects/modules}/core/method.rb +1 -1
  63. data/lib/kind/{type_checkers → objects/modules}/core/module.rb +1 -1
  64. data/lib/kind/{type_checkers → objects/modules}/core/numeric.rb +1 -1
  65. data/lib/kind/{type_checkers → objects/modules}/core/proc.rb +1 -1
  66. data/lib/kind/{type_checkers → objects/modules}/core/queue.rb +1 -1
  67. data/lib/kind/{type_checkers → objects/modules}/core/range.rb +1 -1
  68. data/lib/kind/{type_checkers → objects/modules}/core/regexp.rb +1 -1
  69. data/lib/kind/{type_checkers → objects/modules}/core/string.rb +3 -1
  70. data/lib/kind/{type_checkers → objects/modules}/core/struct.rb +1 -1
  71. data/lib/kind/{type_checkers → objects/modules}/core/symbol.rb +1 -1
  72. data/lib/kind/{type_checkers → objects/modules}/core/time.rb +1 -1
  73. data/lib/kind/{type_checkers → objects/modules}/custom/boolean.rb +2 -2
  74. data/lib/kind/{type_checkers → objects/modules}/custom/callable.rb +1 -1
  75. data/lib/kind/{type_checkers → objects/modules}/custom/lambda.rb +1 -1
  76. data/lib/kind/{type_checkers → objects/modules}/stdlib/open_struct.rb +3 -1
  77. data/lib/kind/{type_checkers → objects/modules}/stdlib/set.rb +3 -1
  78. data/lib/kind/objects/nil.rb +17 -0
  79. data/lib/kind/objects/not_nil.rb +9 -0
  80. data/lib/kind/objects/object.rb +56 -0
  81. data/lib/kind/objects/respond_to.rb +30 -0
  82. data/lib/kind/objects/union_type.rb +44 -0
  83. data/lib/kind/presence.rb +4 -2
  84. data/lib/kind/result.rb +31 -0
  85. data/lib/kind/result/abstract.rb +53 -0
  86. data/lib/kind/result/failure.rb +33 -0
  87. data/lib/kind/result/methods.rb +17 -0
  88. data/lib/kind/result/monad.rb +74 -0
  89. data/lib/kind/result/monad/wrapper.rb +19 -0
  90. data/lib/kind/result/success.rb +53 -0
  91. data/lib/kind/strict/disabled.rb +34 -0
  92. data/lib/kind/try.rb +22 -10
  93. data/lib/kind/validator.rb +111 -0
  94. data/lib/kind/version.rb +1 -1
  95. metadata +83 -42
  96. data/lib/kind/active_model/kind_validator.rb +0 -103
  97. data/lib/kind/core.rb +0 -8
  98. data/lib/kind/core/kind.rb +0 -61
  99. data/lib/kind/core/undefined.rb +0 -7
  100. data/lib/kind/error.rb +0 -15
  101. data/lib/kind/maybe/result.rb +0 -51
  102. data/lib/kind/type_checker.rb +0 -87
  103. data/lib/kind/type_checkers.rb +0 -30
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+
5
+ module Kind
6
+ module Either
7
+ require 'kind/either/monad'
8
+ require 'kind/either/left'
9
+ require 'kind/either/right'
10
+ require 'kind/either/methods'
11
+
12
+ extend self
13
+
14
+ def new(value)
15
+ Right[value]
16
+ end
17
+
18
+ alias_method :[], :new
19
+
20
+ def self.from
21
+ result = yield
22
+
23
+ Either::Monad === result ? result : Either::Right[result]
24
+ rescue StandardError => e
25
+ Either::Left[e]
26
+ end
27
+ end
28
+
29
+ extend Either::Methods
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Either::Left < Either::Monad
5
+ def left?
6
+ true
7
+ end
8
+
9
+ def value_or(default = UNDEFINED, &block)
10
+ Error.invalid_default_arg! if UNDEFINED == default && !block
11
+
12
+ UNDEFINED != default ? default : block.call(value)
13
+ end
14
+
15
+ def map(&_)
16
+ self
17
+ end
18
+
19
+ alias_method :map!, :map
20
+ alias_method :then, :map
21
+ alias_method :then!, :map
22
+ alias_method :and_then, :map
23
+ alias_method :and_then!, :map
24
+
25
+ def inspect
26
+ '#<%s value=%p>' % ['Kind::Left', value]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Either::Methods
5
+ def Left(value)
6
+ Either::Left[value]
7
+ end
8
+
9
+ def Right(value)
10
+ Either::Right[value]
11
+ end
12
+
13
+ def self.included(base)
14
+ base.send(:private, :Left, :Right)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Either::Monad
5
+ require 'kind/either/monad/wrapper'
6
+
7
+ attr_reader :value
8
+
9
+ singleton_class.send(:alias_method, :[], :new)
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def left?
16
+ false
17
+ end
18
+
19
+ def right?
20
+ false
21
+ end
22
+
23
+ def value_or(_method_name = UNDEFINED, &block)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def map(&_)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ alias_method :map!, :map
32
+ alias_method :then, :map
33
+ alias_method :then!, :map
34
+ alias_method :and_then, :map
35
+ alias_method :and_then!, :map
36
+
37
+ def on
38
+ monad = Wrapper.new(self)
39
+
40
+ yield(monad)
41
+
42
+ monad.output
43
+ end
44
+
45
+ def on_right(matcher = UNDEFINED)
46
+ yield(value) if right? && either?(matcher)
47
+
48
+ self
49
+ end
50
+
51
+ def on_left(matcher = UNDEFINED)
52
+ yield(value) if left? && either?(matcher)
53
+
54
+ self
55
+ end
56
+
57
+ def either?(matcher)
58
+ UNDEFINED == matcher || matcher === value
59
+ end
60
+
61
+ def ===(monad)
62
+ self.class === monad && self.value === monad.value
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ require 'kind/monad'
5
+
6
+ class Either::Monad::Wrapper < Kind::Monad::Wrapper
7
+ def left(matcher = UNDEFINED)
8
+ return if @monad.right? || output?
9
+
10
+ @output = yield(@monad.value) if @monad.either?(matcher)
11
+ end
12
+
13
+ def right(matcher = UNDEFINED)
14
+ return if @monad.left? || output?
15
+
16
+ @output = yield(@monad.value) if @monad.either?(matcher)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ class Either::Right < Either::Monad
5
+ def right?
6
+ true
7
+ end
8
+
9
+ def value_or(_default = UNDEFINED, &block)
10
+ @value
11
+ end
12
+
13
+ def map(&fn)
14
+ map!(&fn)
15
+ rescue Kind::Monad::Error => e
16
+ raise e
17
+ rescue StandardError => e
18
+ Either::Left[e]
19
+ end
20
+
21
+ def map!(&fn)
22
+ monad = fn.call(@value)
23
+
24
+ return monad if Either::Monad === monad
25
+
26
+ raise Kind::Monad::Error.new('Kind::Right | Kind::Left', monad)
27
+ end
28
+
29
+ alias_method :then, :map
30
+ alias_method :then!, :map!
31
+ alias_method :and_then, :map
32
+ alias_method :and_then!, :map!
33
+
34
+ def inspect
35
+ '#<%s value=%p>' % ['Kind::Right', value]
36
+ end
37
+ end
38
+ end
data/lib/kind/empty.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Kind
4
6
  module Empty
5
7
  SET = ::Set.new.freeze
data/lib/kind/enum.rb ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'kind/basic'
5
+
6
+ module Kind
7
+ module Enum
8
+ require 'kind/enum/item'
9
+ require 'kind/enum/methods'
10
+
11
+ extend self
12
+
13
+ def values(input)
14
+ enum_module = ::Module.new
15
+
16
+ enum_items =
17
+ case input
18
+ when ::Hash then create_from_hash(input)
19
+ when ::Array then create_from_array(input)
20
+ else raise ArgumentError, 'use an array or hash to define a Kind::Enum'
21
+ end
22
+
23
+ enum_items.each { |item| enum_module.const_set(item.name, item) }
24
+
25
+ enum_map = enum_items.each_with_object({}) do |item, memo|
26
+ memo[item.to_s] = item
27
+ memo[item.value] = item
28
+ memo[item.to_sym] = item
29
+ end
30
+
31
+ enum_module.const_set(:ENUM__MAP, enum_map)
32
+ enum_module.const_set(:ENUM__HASH, enum_items.map(&:to_ary).to_h.freeze)
33
+ enum_module.const_set(:ENUM__KEYS, ::Set.new(enum_items.map(&:key)).freeze)
34
+ enum_module.const_set(:ENUM__VALS, ::Set.new(enum_items.map(&:value)).freeze)
35
+ enum_module.const_set(:ENUM__REFS, ::Set.new(enum_map.keys))
36
+ enum_module.const_set(:ENUM__ITEMS, enum_items.freeze)
37
+
38
+ enum_module.send(:private_constant, :ENUM__MAP, :ENUM__HASH, :ENUM__KEYS,
39
+ :ENUM__VALS, :ENUM__REFS, :ENUM__ITEMS)
40
+
41
+ enum_module.module_eval(METHODS)
42
+
43
+ enum_module.extend(enum_module)
44
+ enum_module
45
+ end
46
+
47
+ private
48
+
49
+ def create_from_hash(input)
50
+ input.map { |key, value| build_item(key, value) }
51
+ end
52
+
53
+ def create_from_array(input)
54
+ input.map.with_index { |key, index| build_item(key, index) }
55
+ end
56
+
57
+ def build_item(key, value)
58
+ return Item.new(key, value) if key.respond_to?(:to_sym)
59
+
60
+ raise ArgumentError, 'use a string or symbol to define a Kind::Enum item'
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Enum
5
+ class Item
6
+ Underscore = ->(arg) do
7
+ str = String(arg).strip
8
+ str.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
9
+ str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
10
+ str.tr!("-", "_")
11
+ str.downcase!
12
+ str
13
+ end
14
+
15
+ attr_reader :value, :to_s, :name, :to_sym, :inspect
16
+
17
+ alias_method :key, :to_s
18
+ alias_method :to_str, :to_s
19
+
20
+ def initialize(key, val)
21
+ @value = val.frozen? ? val : val.dup.freeze
22
+
23
+ @to_s = Kind.respond_to(key, :to_sym).to_s
24
+ @name = Underscore[key].upcase.freeze
25
+ @to_sym = key.to_sym
26
+ @inspect = ('#<Kind::Enum::Item name=%p to_s=%p value=%p>' % [@name, @to_s, @value]).freeze
27
+ end
28
+
29
+ def ==(arg)
30
+ arg == value || arg == to_s || arg == to_sym
31
+ end
32
+
33
+ def to_ary
34
+ [key, value]
35
+ end
36
+
37
+ alias_method :===, :==
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kind
4
+ module Enum
5
+ METHODS = \
6
+ <<-RUBY
7
+ def to_h
8
+ ENUM__HASH
9
+ end
10
+
11
+ def items
12
+ ENUM__ITEMS
13
+ end
14
+
15
+ def ===(arg)
16
+ ENUM__ITEMS.any? { |item| item === arg }
17
+ end
18
+
19
+ def refs
20
+ ENUM__REFS.to_a
21
+ end
22
+
23
+ def keys
24
+ ENUM__KEYS.to_a
25
+ end
26
+
27
+ def values
28
+ ENUM__VALS.to_a
29
+ end
30
+
31
+ def ref?(arg)
32
+ ENUM__REFS.include?(arg)
33
+ end
34
+
35
+ def key?(arg)
36
+ arg.respond_to?(:to_sym) ? ref?(arg) && !value?(arg) : false
37
+ end
38
+
39
+ def value?(arg)
40
+ ENUM__VALS.include?(arg)
41
+ end
42
+
43
+ def [](arg)
44
+ return arg if ref?(arg)
45
+
46
+ raise KeyError, "key or value not found: %p" % [arg]
47
+ end
48
+
49
+ def ref(arg)
50
+ arg if ref?(arg)
51
+ end
52
+
53
+ def item(arg)
54
+ ENUM__MAP[arg]
55
+ end
56
+
57
+ def key(arg)
58
+ item(arg).key if value?(arg)
59
+ end
60
+
61
+ def value_at(arg)
62
+ item(arg).value if key?(arg)
63
+ end
64
+
65
+ def self.included(base)
66
+ base.extend(base)
67
+ end
68
+ RUBY
69
+
70
+ private_constant :METHODS
71
+ end
72
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+
5
+ module Kind
6
+ module Function
7
+ module Behavior
8
+ def self.extended(base)
9
+ base.send(:alias_method, :[], :call)
10
+ base.send(:alias_method, :===, :call)
11
+ base.send(:alias_method, :yield, :call)
12
+ end
13
+ end
14
+
15
+ def self.included(_)
16
+ raise RuntimeError, "The Kind::Function can't be included, it can be only extended."
17
+ end
18
+
19
+ def self.extended(base)
20
+ base.extend(Kind.of_module(base))
21
+ end
22
+
23
+ def kind_function!
24
+ return self if Kind.is?(Behavior, self)
25
+
26
+ KIND.respond_to!(:call, self).extend(Behavior)
27
+
28
+ if method(:call).parameters.empty?
29
+ raise ArgumentError, "#{self.name}.call must receive at least one argument"
30
+ end
31
+
32
+ self.instance_eval(
33
+ 'def to_proc; @to_proc ||= method(:call).to_proc; end' \
34
+ "\n" \
35
+ 'def curry; @curry ||= to_proc.curry; end'
36
+ )
37
+
38
+ self.to_proc
39
+ self.curry
40
+ self
41
+ end
42
+
43
+ private_constant :Behavior
44
+ end
45
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/basic'
4
+ require 'kind/empty'
5
+ require 'kind/__lib__/attributes'
6
+
7
+ module Kind
8
+ module Functional
9
+ def self.extended(_)
10
+ raise RuntimeError, "The Kind::Functional can't be extended, it can be only included."
11
+ end
12
+
13
+ def self.included(base)
14
+ Kind.of_class(base).send(:extend, ClassMethods)
15
+ end
16
+
17
+ module Behavior
18
+ def self.included(base)
19
+ base.send(:alias_method, :[], :call)
20
+ base.send(:alias_method, :===, :call)
21
+ base.send(:alias_method, :yield, :call)
22
+ end
23
+
24
+ def initialize(arg = Empty::HASH)
25
+ hash = STRICT.kind_of(::Hash, arg)
26
+
27
+ self.class.__dependencies__.each do |name, (kind, default, _visibility)|
28
+ value_to_assign = ATTRIBUTES.value_to_assign!(kind, default, hash, name)
29
+
30
+ instance_variable_set("@#{name}", value_to_assign)
31
+ end
32
+ end
33
+ end
34
+
35
+ module DependencyInjection
36
+ def __dependencies__ # :nodoc:
37
+ @__dependencies__ ||= {}
38
+ end
39
+
40
+ def dependency(name, kind, default: UNDEFINED)
41
+ __dependencies__[ATTRIBUTES.name!(name)] = ATTRIBUTES.value!(kind, default)
42
+
43
+ attr_reader(name)
44
+
45
+ private(name)
46
+
47
+ name
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ include DependencyInjection
53
+
54
+ def kind_functional!
55
+ return self if Kind.is?(Behavior, self)
56
+
57
+ public_methods = self.public_instance_methods - ::Object.new.methods
58
+
59
+ unless public_methods.include?(:call)
60
+ raise Kind::Error.new("expected #{self} to implement `#call`")
61
+ end
62
+
63
+ if public_methods.size > 1
64
+ raise Kind::Error.new("#{self} can only have `#call` as its public method")
65
+ end
66
+
67
+ if public_instance_method(:call).parameters.empty?
68
+ raise ArgumentError, "#{self.name}#call must receive at least one argument"
69
+ end
70
+
71
+ self.send(:include, Behavior)
72
+
73
+ def self.inherited(_)
74
+ raise RuntimeError, "#{self.name} is a Kind::Functional and it can't be inherited by anyone"
75
+ end
76
+
77
+ self.class_eval(
78
+ 'def to_proc; @to_proc ||= method(:call).to_proc; end' \
79
+ "\n" \
80
+ 'def curry; @curry ||= to_proc.curry; end'
81
+ )
82
+
83
+ __dependencies__.freeze
84
+
85
+ self
86
+ end
87
+ end
88
+ end
89
+ end