functional-light-service 6.1.0 → 6.3.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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/lib/functional-light-service.rb +6 -31
  3. metadata +15 -297
  4. data/.github/workflows/project-build.yml +0 -71
  5. data/.gitignore +0 -22
  6. data/.rspec +0 -3
  7. data/.rubocop.yml +0 -101
  8. data/.solargraph.yml +0 -11
  9. data/AUDIT-functional-light-service.md +0 -352
  10. data/Appraisals +0 -7
  11. data/CHANGELOG.md +0 -273
  12. data/CODE_OF_CONDUCT.md +0 -22
  13. data/Gemfile +0 -4
  14. data/LICENSE +0 -22
  15. data/README.md +0 -1578
  16. data/Rakefile +0 -12
  17. data/VERSION +0 -1
  18. data/audit/bench.rb +0 -99
  19. data/audit/verify_findings.rb +0 -172
  20. data/functional-light-service.gemspec +0 -33
  21. data/gemfiles/dry_inflector_0_2_1.gemfile +0 -5
  22. data/gemfiles/i18n_1_8_11.gemfile +0 -5
  23. data/lib/functional-light-service/action.rb +0 -97
  24. data/lib/functional-light-service/configuration.rb +0 -26
  25. data/lib/functional-light-service/context/key_verifier.rb +0 -124
  26. data/lib/functional-light-service/context.rb +0 -195
  27. data/lib/functional-light-service/deprecations.rb +0 -26
  28. data/lib/functional-light-service/errors.rb +0 -8
  29. data/lib/functional-light-service/functional/enum.rb +0 -286
  30. data/lib/functional-light-service/functional/maybe.rb +0 -21
  31. data/lib/functional-light-service/functional/monad.rb +0 -77
  32. data/lib/functional-light-service/functional/null.rb +0 -88
  33. data/lib/functional-light-service/functional/option.rb +0 -100
  34. data/lib/functional-light-service/functional/result.rb +0 -129
  35. data/lib/functional-light-service/functional/sequencer.rb +0 -144
  36. data/lib/functional-light-service/localization_adapter.rb +0 -48
  37. data/lib/functional-light-service/organizer/execute.rb +0 -16
  38. data/lib/functional-light-service/organizer/iterate.rb +0 -30
  39. data/lib/functional-light-service/organizer/reduce_if.rb +0 -19
  40. data/lib/functional-light-service/organizer/reduce_until.rb +0 -22
  41. data/lib/functional-light-service/organizer/scoped_reducable.rb +0 -15
  42. data/lib/functional-light-service/organizer/with_callback.rb +0 -28
  43. data/lib/functional-light-service/organizer/with_reducer.rb +0 -81
  44. data/lib/functional-light-service/organizer/with_reducer_factory.rb +0 -20
  45. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +0 -110
  46. data/lib/functional-light-service/organizer.rb +0 -114
  47. data/lib/functional-light-service/testing/context_factory.rb +0 -48
  48. data/lib/functional-light-service/testing.rb +0 -3
  49. data/lib/functional-light-service/version.rb +0 -5
  50. data/resources/fail_actions.png +0 -0
  51. data/resources/light-service.png +0 -0
  52. data/resources/organizer_and_actions.png +0 -0
  53. data/resources/skip_actions.png +0 -0
  54. data/spec/acceptance/add_numbers_spec.rb +0 -11
  55. data/spec/acceptance/after_actions_spec.rb +0 -87
  56. data/spec/acceptance/around_each_spec.rb +0 -19
  57. data/spec/acceptance/before_actions_spec.rb +0 -115
  58. data/spec/acceptance/custom_log_from_organizer_spec.rb +0 -61
  59. data/spec/acceptance/deprecation_warnings_spec.rb +0 -82
  60. data/spec/acceptance/fail_spec.rb +0 -52
  61. data/spec/acceptance/log_from_organizer_spec.rb +0 -154
  62. data/spec/acceptance/message_localization_spec.rb +0 -119
  63. data/spec/acceptance/organizer/add_aliases_spec.rb +0 -28
  64. data/spec/acceptance/organizer/add_to_context_spec.rb +0 -30
  65. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +0 -42
  66. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +0 -68
  67. data/spec/acceptance/organizer/execute_spec.rb +0 -46
  68. data/spec/acceptance/organizer/iterate_spec.rb +0 -44
  69. data/spec/acceptance/organizer/reduce_if_spec.rb +0 -89
  70. data/spec/acceptance/organizer/reduce_until_spec.rb +0 -49
  71. data/spec/acceptance/organizer/with_callback_spec.rb +0 -113
  72. data/spec/acceptance/organizer_entry_point_spec.rb +0 -35
  73. data/spec/acceptance/rollback_spec.rb +0 -183
  74. data/spec/acceptance/testing/context_factory_spec.rb +0 -54
  75. data/spec/action_expected_keys_spec.rb +0 -63
  76. data/spec/action_expects_and_promises_spec.rb +0 -97
  77. data/spec/action_promised_keys_spec.rb +0 -126
  78. data/spec/action_spec.rb +0 -97
  79. data/spec/context/inspect_spec.rb +0 -42
  80. data/spec/context_spec.rb +0 -289
  81. data/spec/examples/amount_spec.rb +0 -77
  82. data/spec/examples/controller_spec.rb +0 -63
  83. data/spec/examples/validate_address_spec.rb +0 -38
  84. data/spec/lib/deterministic/class_mixin_spec.rb +0 -24
  85. data/spec/lib/deterministic/currify_spec.rb +0 -90
  86. data/spec/lib/deterministic/monad_axioms.rb +0 -46
  87. data/spec/lib/deterministic/monad_spec.rb +0 -47
  88. data/spec/lib/deterministic/null_spec.rb +0 -65
  89. data/spec/lib/deterministic/option_spec.rb +0 -140
  90. data/spec/lib/deterministic/result/failure_spec.rb +0 -65
  91. data/spec/lib/deterministic/result/result_map_spec.rb +0 -155
  92. data/spec/lib/deterministic/result/result_shared.rb +0 -25
  93. data/spec/lib/deterministic/result/success_spec.rb +0 -41
  94. data/spec/lib/deterministic/result_spec.rb +0 -63
  95. data/spec/lib/deterministic/sequencer_spec.rb +0 -506
  96. data/spec/lib/edge_cases_spec.rb +0 -156
  97. data/spec/lib/enum_spec.rb +0 -114
  98. data/spec/lib/native_pattern_matching_spec.rb +0 -74
  99. data/spec/localization_adapter_spec.rb +0 -83
  100. data/spec/organizer/with_reducer_spec.rb +0 -56
  101. data/spec/organizer_key_aliases_spec.rb +0 -29
  102. data/spec/organizer_spec.rb +0 -115
  103. data/spec/readme_spec.rb +0 -45
  104. data/spec/sample/calculates_order_tax_action_spec.rb +0 -16
  105. data/spec/sample/calculates_tax_spec.rb +0 -30
  106. data/spec/sample/looks_up_tax_percentage_action_spec.rb +0 -55
  107. data/spec/sample/provides_free_shipping_action_spec.rb +0 -25
  108. data/spec/sample/tax/calculates_order_tax_action.rb +0 -10
  109. data/spec/sample/tax/calculates_tax.rb +0 -11
  110. data/spec/sample/tax/looks_up_tax_percentage_action.rb +0 -28
  111. data/spec/sample/tax/provides_free_shipping_action.rb +0 -11
  112. data/spec/spec_helper.rb +0 -29
  113. data/spec/support.rb +0 -1
  114. data/spec/test_doubles.rb +0 -628
  115. data/spec/testing/context_factory/iterate_spec.rb +0 -39
  116. data/spec/testing/context_factory/reduce_if_spec.rb +0 -40
  117. data/spec/testing/context_factory/reduce_until_spec.rb +0 -40
  118. data/spec/testing/context_factory/with_callback_spec.rb +0 -38
  119. data/spec/testing/context_factory_spec.rb +0 -76
@@ -1,195 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- # rubocop:disable Metrics/ClassLength
5
- class Context < Hash
6
- include FunctionalLightService::Prelude::Option
7
- include FunctionalLightService::Prelude::Result
8
-
9
- attr_reader :outcome
10
- attr_accessor :current_action, :organized_by
11
-
12
- # rubocop:disable Lint/MissingSuper
13
- def initialize(context = {},
14
- outcome = Success(:message => '', :error => nil))
15
- @outcome = outcome
16
- @skip_remaining = false
17
- context.to_hash.each { |k, v| self[k] = v }
18
- end
19
- # rubocop:enable Lint/MissingSuper
20
-
21
- def self.make(context = {})
22
- unless context.is_a?(Hash) || context.is_a?(FunctionalLightService::Context)
23
- msg = 'Argument must be Hash or FunctionalLightService::Context'
24
- raise ArgumentError, msg
25
- end
26
-
27
- context = new(context) unless context.is_a?(Context)
28
-
29
- context.assign_aliases(context.delete(:_aliases)) if context[:_aliases]
30
- context
31
- end
32
-
33
- def add_to_context(values)
34
- merge! values
35
- end
36
-
37
- def success?
38
- @outcome.success?
39
- end
40
-
41
- def failure?
42
- @outcome.failure?
43
- end
44
-
45
- def skip_remaining?
46
- @skip_remaining
47
- end
48
-
49
- def reset_skip_remaining!
50
- # Resetta soltanto il flag: l'esito (e il suo messaggio) non vanno persi
51
- @skip_remaining = false
52
- end
53
-
54
- def message
55
- @outcome.value[:message]
56
- end
57
-
58
- def error_code
59
- @outcome.value[:error]
60
- end
61
-
62
- def succeed!(message = nil, options = {})
63
- message = Configuration.localization_adapter.success(message,
64
- current_action,
65
- options)
66
- @outcome = Success(:message => message)
67
- end
68
-
69
- def fail!(message = nil, options_or_error_code = {})
70
- options_or_error_code ||= {}
71
-
72
- if options_or_error_code.is_a?(Hash)
73
- # dup: l'hash di opzioni appartiene al chiamante e non va mutato
74
- options = options_or_error_code.dup
75
- error_code = options.delete(:error_code)
76
- else
77
- error_code = options_or_error_code
78
- options = {}
79
- end
80
-
81
- message = Configuration.localization_adapter.failure(message,
82
- current_action,
83
- options)
84
-
85
- @outcome = Failure(:message => message, :error => error_code)
86
- end
87
-
88
- def fail_and_return!(*args)
89
- fail!(*args)
90
- throw(:jump_when_failed)
91
- end
92
-
93
- def fail_with_rollback!(message = nil, error_code = nil)
94
- fail!(message, error_code)
95
- raise FailWithRollbackError
96
- end
97
-
98
- def skip_remaining!(message = nil)
99
- @outcome = Success(:message => message)
100
- @skip_remaining = true
101
- end
102
-
103
- def stop_processing?
104
- failure? || skip_remaining?
105
- end
106
-
107
- # Registra le chiavi come accessor consentiti: la lettura/scrittura passa
108
- # da method_missing con whitelist. Rispetto a define_singleton_method non
109
- # materializza una singleton class per ogni context (audit, finding 3.3)
110
- def define_accessor_methods_for_keys(keys)
111
- return if keys.nil?
112
-
113
- @accessor_methods ||= {}
114
- keys.each do |key|
115
- key = key.to_sym
116
- next if @accessor_methods.key?(key)
117
-
118
- # Prima il conflitto veniva saltato in silenzio e ctx.size (o :count,
119
- # :message, ...) ritornava il metodo di Hash invece del valore
120
- if respond_to?(key) || respond_to?("#{key}=")
121
- raise ReservedKeysInContextError,
122
- "expected or promised key :#{key} conflicts with an existing " \
123
- "#{self.class.name} method: rename the key or access it via ctx[:#{key}]"
124
- end
125
-
126
- @accessor_methods[key] = [:reader, key]
127
- @accessor_methods[:"#{key}="] = [:writer, key]
128
- end
129
- end
130
-
131
- def method_missing(name, *args)
132
- accessor = @accessor_methods && @accessor_methods[name]
133
- return super unless accessor
134
-
135
- accessor[0] == :reader ? fetch(accessor[1]) : self[accessor[1]] = args.first
136
- end
137
-
138
- def respond_to_missing?(name, _include_all = false)
139
- (!@accessor_methods.nil? && @accessor_methods.key?(name)) || super
140
- end
141
-
142
- def assign_aliases(aliases)
143
- @aliases = aliases
144
- # Hash inverso precomputato: la risoluzione in lettura/scrittura
145
- # resta O(1) invece del reverse-scan di Hash#key
146
- @inverse_aliases = aliases.invert
147
-
148
- self
149
- end
150
-
151
- def aliases
152
- @aliases ||= {}
153
- end
154
-
155
- def [](key)
156
- super(resolve_key(key))
157
- end
158
-
159
- def []=(key, value)
160
- super(resolve_key(key), value)
161
- end
162
-
163
- def fetch(key, ...)
164
- super(resolve_key(key), ...)
165
- end
166
-
167
- def key?(key)
168
- super(resolve_key(key))
169
- end
170
-
171
- alias has_key? key?
172
- alias member? key?
173
- alias include? key?
174
-
175
- def inspect
176
- "#{self.class}(#{self}, success: #{success?}, message: #{check_nil(message)}, error_code: " \
177
- "#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, aliases: #{aliases})"
178
- end
179
-
180
- private
181
-
182
- def resolve_key(key)
183
- return key unless @inverse_aliases
184
-
185
- @inverse_aliases[key] || key
186
- end
187
-
188
- def check_nil(value)
189
- return 'nil' unless value
190
-
191
- "'#{value}'"
192
- end
193
- end
194
- # rubocop:enable Metrics/ClassLength
195
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- # Deprecation warnings non fatali: ogni messaggio viene emesso una sola
5
- # volta per processo (su stderr) e puo' essere silenziato globalmente,
6
- # ad esempio nelle suite di test.
7
- module Deprecations
8
- @emitted = {}
9
-
10
- class << self
11
- attr_accessor :silenced
12
-
13
- def warn(message)
14
- return if silenced
15
- return if @emitted[message]
16
-
17
- @emitted[message] = true
18
- Kernel.warn("DEPRECATION WARNING: #{message}")
19
- end
20
-
21
- def reset!
22
- @emitted = {}
23
- end
24
- end
25
- end
26
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- class FailWithRollbackError < StandardError; end
5
- class ExpectedKeysNotInContextError < StandardError; end
6
- class PromisedKeysNotInContextError < StandardError; end
7
- class ReservedKeysInContextError < StandardError; end
8
- end
@@ -1,286 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- module Enum
5
- class MatchError < StandardError; end
6
- end
7
-
8
- class EnumBuilder
9
- def initialize(parent)
10
- @parent = parent
11
- end
12
-
13
- class DataType
14
- module AnyEnum
15
- include FunctionalLightService::Monad
16
-
17
- def match(&)
18
- parent.match(self, &)
19
- end
20
-
21
- def to_s
22
- value.to_s
23
- end
24
-
25
- def name
26
- self.class.name.split("::")[-1]
27
- end
28
-
29
- # Returns array. Will fail on Nullary objects.
30
- # TODO: define a Unary module so we can define this method differently on Unary vs Binary
31
- def wrapped_values
32
- if is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
33
- value.values
34
- else
35
- [value]
36
- end
37
- end
38
-
39
- # Supporto al pattern matching nativo di Ruby (case/in):
40
- # case result
41
- # in FunctionalLightService::Result::Success(s) then ...
42
- # in FunctionalLightService::Result::Failure(f) then ...
43
- # end
44
- def deconstruct
45
- is_a?(FunctionalLightService::EnumBuilder::DataType::Nullary) ? [] : wrapped_values
46
- end
47
-
48
- def deconstruct_keys(_keys)
49
- if is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
50
- value.dup
51
- elsif is_a?(FunctionalLightService::EnumBuilder::DataType::Nullary)
52
- {}
53
- else
54
- { args[0] => value }
55
- end
56
- end
57
- end
58
-
59
- module Nullary
60
- def initialize(*_args)
61
- @value = nil
62
- end
63
-
64
- def inspect
65
- name
66
- end
67
- end
68
-
69
- # TODO: this should probably be named Multary
70
- module Binary
71
- def initialize(*init)
72
- unless (init.one? && init[0].is_a?(Hash)) || init.count == args.count
73
- raise ArgumentError, "Expected arguments for #{args}, got #{init}"
74
- end
75
-
76
- @value = if init.one? && init[0].is_a?(Hash)
77
- args.zip(init[0].values).to_h
78
- else
79
- args.zip(init).to_h
80
- end
81
- end
82
-
83
- def inspect
84
- params = value.map { |k, v| "#{k}: #{v.inspect}" }
85
- "#{name}(#{params.join(', ')})"
86
- end
87
- end
88
-
89
- # rubocop:disable Metrics/MethodLength
90
- def self.create(parent, args)
91
- if args.include? :value
92
- raise ArgumentError, "#{args} may not contain the reserved name :value"
93
- end
94
-
95
- dt = Class.new(parent)
96
-
97
- dt.instance_eval do
98
- public_class_method :new
99
- include AnyEnum
100
-
101
- define_method(:args) { args }
102
-
103
- define_method(:parent) { parent }
104
- private :parent
105
- end
106
-
107
- case args.count
108
- when 0
109
- dt.instance_eval do
110
- include Nullary
111
-
112
- private :value
113
- end
114
- when 1
115
- dt.instance_eval do
116
- define_method(args[0].to_sym) { value }
117
- end
118
- else
119
- dt.instance_eval do
120
- include Binary
121
-
122
- args.each do |m|
123
- define_method(m) do
124
- @value[m]
125
- end
126
- end
127
- end
128
- end
129
-
130
- dt
131
- end
132
- # rubocop:enable Metrics/MethodLength
133
-
134
- class << self
135
- public :new
136
- end
137
- end
138
-
139
- def method_missing(m, *args)
140
- if @parent.const_defined?(m)
141
- raise ArgumentError, "variant #{m} is already defined for this enum"
142
- end
143
-
144
- @parent.const_set(m, DataType.create(@parent, args))
145
- end
146
-
147
- def respond_to_missing?(_m, _include_all = false)
148
- true
149
- end
150
- end
151
-
152
- module_function
153
-
154
- # rubocop:disable Metrics/AbcSize
155
- # rubocop:disable Metrics/CyclomaticComplexity
156
- # rubocop:disable Metrics/MethodLength
157
- # rubocop:disable Metrics/PerceivedComplexity
158
- def enum(&block)
159
- mod = Class.new do # the enum to be built
160
- private_class_method :new
161
-
162
- def self.match(obj, &block)
163
- # Binding#receiver: stesso risultato di binding.eval('self') senza eval
164
- caller_ctx = block.binding.receiver
165
-
166
- matcher = self::Matcher.new(obj)
167
- matcher.instance_eval(&block)
168
-
169
- # exhaustiveness check su classi memoizzate: niente split/sort di
170
- # stringhe per chiamata
171
- covered = matcher.matches.map { |e| e[1] }
172
- missing = variant_classes.reject { |klass| covered.include?(klass) }
173
- unless missing.empty?
174
- missing_names = missing.map { |klass| klass.name.split('::')[-1].to_sym }
175
- raise Enum::MatchError, "Match is non-exhaustive, #{missing_names} not covered"
176
- end
177
-
178
- type_matches = matcher.matches.select { |r| r[0].is_a?(r[1]) }
179
-
180
- type_matches.each do |match|
181
- obj, _type, block, args, guard = match
182
-
183
- return caller_ctx.instance_eval(&block) if args.empty?
184
-
185
- if args.count != obj.args.count
186
- msg = "Pattern (#{args.join(', ')}) must match (#{obj.args.join(', ')})"
187
- raise Enum::MatchError, msg
188
- end
189
-
190
- guard_ctx = guard_context(obj, args)
191
- return caller_ctx.instance_exec(* obj.wrapped_values, &block) unless guard
192
-
193
- if guard && guard_ctx.instance_exec(obj, &guard)
194
- return caller_ctx.instance_exec(* obj.wrapped_values, &block)
195
- end
196
- end
197
-
198
- raise Enum::MatchError, "No match could be made"
199
- end
200
-
201
- def self.variants
202
- constants - %i[Matcher MatchError]
203
- end
204
-
205
- def self.variant_classes
206
- @variant_classes ||= variants.map { |v| const_get(v) }.freeze
207
- end
208
-
209
- def self.guard_context(obj, args)
210
- # Struct.new definisce una classe: va fatto una volta per firma,
211
- # non a ogni match con guard
212
- @guard_structs ||= {}
213
- struct = @guard_structs[args] ||= Struct.new(*args)
214
-
215
- if obj.is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
216
- struct.new(*obj.value.values)
217
- else
218
- struct.new(obj.value)
219
- end
220
- end
221
- end
222
- enum = EnumBuilder.new(mod)
223
- enum.instance_eval(&block)
224
-
225
- type_variants = mod.constants
226
-
227
- matcher = Class.new do
228
- def initialize(obj)
229
- @obj = obj
230
- @matches = []
231
- @vars = []
232
- end
233
-
234
- attr_reader :matches, :vars
235
-
236
- def where(&guard)
237
- guard
238
- end
239
-
240
- type_variants.each do |m|
241
- define_method(m) do |guard = nil, &inner_block|
242
- raise ArgumentError, "No block given to `#{m}`" if inner_block.nil?
243
-
244
- params_spec = inner_block.parameters
245
- if params_spec.any? { |spec| spec.size < 2 }
246
- msg = "Unnamed param found in block parameters: #{params_spec.inspect}"
247
- raise ArgumentError, msg
248
- end
249
- if params_spec.any? { |spec| spec[0] != :req && spec[0] != :opt }
250
- msg = "Only :req & :opt params allowed; parameters=#{params_spec.inspect}"
251
- raise ArgumentError, msg
252
- end
253
-
254
- args = params_spec.map { |spec| spec[1] }
255
-
256
- type = mod.const_get(m)
257
-
258
- guard = nil if guard && !guard.is_a?(Proc)
259
-
260
- @matches << [@obj, type, inner_block, args, guard]
261
- end
262
- end
263
- end
264
-
265
- mod.const_set(:Matcher, matcher)
266
-
267
- type_variants.each do |variant|
268
- mod.singleton_class.class_exec do
269
- define_method(variant) do |*args|
270
- const_get(variant).new(*args)
271
- end
272
- end
273
- end
274
- mod
275
- end
276
- # rubocop:enable Metrics/AbcSize
277
- # rubocop:enable Metrics/CyclomaticComplexity
278
- # rubocop:enable Metrics/MethodLength
279
- # rubocop:enable Metrics/PerceivedComplexity
280
-
281
- def impl(enum_type, &block)
282
- enum_type.variants.each do |v|
283
- enum_type.const_get(v).class_eval(&block)
284
- end
285
- end
286
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Object
4
- def null?
5
- false
6
- end
7
-
8
- def some?
9
- true
10
- end
11
- end
12
-
13
- # rubocop:disable Naming/MethodName
14
- def Maybe(obj)
15
- FunctionalLightService::Deprecations.warn(
16
- "Maybe()/Null are deprecated and will be removed in a future release; " \
17
- "use FunctionalLightService::Option (Some/None) instead"
18
- )
19
- obj.nil? ? Null.instance : obj
20
- end
21
- # rubocop:enable Naming/MethodName
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FunctionalLightService
4
- module Monad
5
- class NotMonadError < StandardError; end
6
-
7
- # Basicly the `pure` function
8
- def initialize(init)
9
- @value = join(init)
10
- end
11
-
12
- # If the passed value is monad already, get the value to avoid nesting
13
- # M[M[A]] is equivalent to M[A]
14
- def join(other)
15
- if other.is_a? self.class
16
- other.value
17
- else
18
- other
19
- end
20
- end
21
-
22
- # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
23
- # boxes it back to the same monad (Mb)
24
- # fmap :: (a -> b) -> M a -> M b
25
- def fmap(proc = nil, &block)
26
- result = (proc || block).call(value)
27
- self.class.new(result)
28
- end
29
-
30
- # The monad: takes a function which returns a monad (of the same type), applies the function
31
- # bind :: (a -> Mb) -> M a -> M b
32
- # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
33
- def bind(proc = nil, &block)
34
- (proc || block).call(value).tap do |result|
35
- # rubocop:disable Style/CaseEquality
36
- parent = self.class.superclass === Object ? self.class : self.class.superclass
37
- # rubocop:enable Style/CaseEquality
38
- unless result.is_a? parent
39
- raise NotMonadError, "Expected #{result.inspect} to be an #{parent}"
40
- end
41
- end
42
- end
43
- # rubocop:disable Naming/MethodName
44
- alias :'>>=' :bind
45
- # rubocop:enable Naming/MethodName
46
-
47
- # Get the underlying value, return in Haskell
48
- # return :: M a -> a
49
- def value
50
- @value
51
- end
52
-
53
- def to_s
54
- value.to_s
55
- end
56
-
57
- # Two monads are equivalent if they are of the same type and when their values are equal
58
- def ==(other)
59
- return false unless other.is_a? self.class
60
-
61
- @value == other.monad_value
62
- end
63
-
64
- # Reader protetto: #value e' privato nelle varianti Nullary (es. None),
65
- # ma il confronto tra monadi dello stesso tipo deve poter leggere il valore
66
- def monad_value
67
- @value
68
- end
69
- protected :monad_value
70
-
71
- # Return the string representation of the Monad
72
- def inspect
73
- pretty_class_name = self.class.name.split('::')[-1]
74
- "#{pretty_class_name}(#{value.inspect})"
75
- end
76
- end
77
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The simplest NullObject there can be
4
- class Null
5
- class << self
6
- def method_missing(m, *args)
7
- if m == :new
8
- super
9
- else
10
- Null.instance
11
- end
12
- end
13
-
14
- def respond_to_missing?(m, _include_all = false)
15
- m != :new || super
16
- end
17
-
18
- def instance
19
- FunctionalLightService::Deprecations.warn(
20
- "Maybe()/Null are deprecated and will be removed in a future release; " \
21
- "use FunctionalLightService::Option (Some/None) instead"
22
- )
23
- @instance ||= new([])
24
- end
25
-
26
- def null?
27
- true
28
- end
29
-
30
- def some?
31
- false
32
- end
33
-
34
- def mimic(klas)
35
- FunctionalLightService::Deprecations.warn(
36
- "Maybe()/Null are deprecated and will be removed in a future release; " \
37
- "use FunctionalLightService::Option (Some/None) instead"
38
- )
39
- new(klas.instance_methods(false))
40
- end
41
-
42
- def ==(other)
43
- other.respond_to?(:null?) && other.null?
44
- end
45
- end
46
- private_class_method :new
47
-
48
- def initialize(methods)
49
- @methods = methods
50
- end
51
-
52
- # implicit conversions
53
- def to_str
54
- ''
55
- end
56
-
57
- def to_ary
58
- []
59
- end
60
-
61
- def method_missing(m, *args)
62
- return self if respond_to_missing?(m)
63
-
64
- super
65
- end
66
-
67
- def null?
68
- true
69
- end
70
-
71
- def some?
72
- false
73
- end
74
-
75
- # Convenzione Ruby: si estende respond_to_missing?, mai respond_to?
76
- # (il vecchio override aveva anche la firma sbagliata: mancava include_all)
77
- def respond_to_missing?(m, _include_all = false)
78
- @methods.empty? || @methods.include?(m) || super
79
- end
80
-
81
- def inspect
82
- 'Null'
83
- end
84
-
85
- def ==(other)
86
- other.respond_to?(:null?) && other.null?
87
- end
88
- end