decider 0.9.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.
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decider::View
4
+ class Module < ::Module
5
+ EVOLVE_FALLBACK = proc { [nil, proc { state }] }
6
+
7
+ Evolve = Data.define(:state, :event) do
8
+ def self.build(state, event)
9
+ new(state: state, event: event)
10
+ end
11
+ end
12
+
13
+ def initialize(initial_state:, evolutions:)
14
+ define_method(:initial_state) do
15
+ initial_state
16
+ end
17
+
18
+ define_method(:evolve) do |*args|
19
+ if args.empty?
20
+ ->(state, event) { evolve(state, event) }
21
+ else
22
+ context = Evolve.build(*args)
23
+
24
+ evolutions.find(EVOLVE_FALLBACK) do |args, _|
25
+ case args
26
+ in [Proc => fn]
27
+ context.instance_exec(&fn)
28
+ in [etype]
29
+ context_event = context.event
30
+ context_event in ^etype
31
+ in [stype, etype]
32
+ context_state = context.state
33
+ context_event = context.event
34
+ [context_state, context_event] in [^stype, ^etype]
35
+ else
36
+ false
37
+ end
38
+ end => [_, handler]
39
+
40
+ context.instance_exec(&handler)
41
+ end
42
+ end
43
+
44
+ define_method(:lmap_on_event) do |fn|
45
+ Decider::View.lmap_on_event(fn, self)
46
+ end
47
+
48
+ define_method(:lmap_on_state) do |fn|
49
+ Decider::View.lmap_on_state(fn, self)
50
+ end
51
+
52
+ define_method(:rmap_on_state) do |fn|
53
+ Decider::View.rmap_on_state(fn, self)
54
+ end
55
+
56
+ define_method(:dimap_on_state) do |fl:, fr:|
57
+ Decider::View.dimap_on_state(fl, fr, self)
58
+ end
59
+
60
+ define_method(:many) do
61
+ Decider::View.many(self)
62
+ end
63
+ end
64
+ end
65
+
66
+ class Builder
67
+ DEFAULT = Object.new
68
+
69
+ def initialize
70
+ @initial_state = DEFAULT
71
+ @evolutions = {}
72
+ end
73
+
74
+ def build(&block)
75
+ instance_exec(&block) if block_given?
76
+
77
+ raise StateNotDefined if @initial_state == DEFAULT
78
+
79
+ view = Class.new
80
+
81
+ mod = Module.new(
82
+ initial_state: @initial_state,
83
+ evolutions: evolutions
84
+ )
85
+
86
+ view.extend(mod)
87
+
88
+ view
89
+ end
90
+
91
+ private
92
+
93
+ attr_reader :evolutions
94
+
95
+ def initial_state(state)
96
+ raise StateAlreadyDefined if @initial_state != DEFAULT
97
+
98
+ @initial_state = state
99
+ end
100
+
101
+ def evolve(*args, &block)
102
+ evolutions[args] = block
103
+ end
104
+ end
105
+ private_constant :Builder
106
+
107
+ def self.define(&block)
108
+ builder = Builder.new
109
+ builder.build(&block)
110
+ end
111
+
112
+ def self.lmap_on_event(fn, view)
113
+ define do
114
+ initial_state view.initial_state
115
+
116
+ evolve proc { true } do
117
+ view.evolve(state, fn.call(event))
118
+ end
119
+ end
120
+ end
121
+
122
+ def self.lmap_on_state(fn, view)
123
+ dimap_on_state(fn, ->(state) { state }, view)
124
+ end
125
+
126
+ def self.rmap_on_state(fn, view)
127
+ dimap_on_state(->(state) { state }, fn, view)
128
+ end
129
+
130
+ def self.dimap_on_state(fl, fr, view)
131
+ define do
132
+ initial_state fr.call(view.initial_state)
133
+
134
+ evolve proc { true } do
135
+ fr.call(view.evolve(fl.call(state), event))
136
+ end
137
+ end
138
+ end
139
+
140
+ def self.many(view)
141
+ define do
142
+ initial_state({})
143
+
144
+ evolve proc { [state, event] in [Hash, [_id, _]] } do
145
+ event => [id, event]
146
+
147
+ vs = state.fetch(id) { view.initial_state }
148
+ vs = view.evolve(vs, event)
149
+
150
+ state.merge(id => vs)
151
+ end
152
+ end
153
+ end
154
+ end
data/lib/decider.rb ADDED
@@ -0,0 +1,380 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "decider/reactor"
4
+ require_relative "decider/view"
5
+
6
+ module Decider
7
+ StateAlreadyDefined = Class.new(StandardError)
8
+ StateNotDefined = Class.new(StandardError)
9
+
10
+ Pair = Data.define(:left, :right)
11
+
12
+ class Left
13
+ attr_reader :value
14
+
15
+ def initialize(value)
16
+ @value = value
17
+ end
18
+
19
+ def deconstruct
20
+ [:left, value]
21
+ end
22
+
23
+ def ==(other)
24
+ value == other.value
25
+ end
26
+ end
27
+
28
+ class Right
29
+ attr_reader :value
30
+
31
+ def initialize(value)
32
+ @value = value
33
+ end
34
+
35
+ def deconstruct
36
+ [:right, value]
37
+ end
38
+
39
+ def ==(other)
40
+ value == other.value
41
+ end
42
+ end
43
+
44
+ class Module < ::Module
45
+ DECIDE_FALLBACK = proc { [nil, proc {}] }
46
+ EVOLVE_FALLBACK = proc { [nil, proc { state }] }
47
+
48
+ Decide = Data.define(:command, :state, :_events) do
49
+ def emit(*events)
50
+ _events.push(*events)
51
+ end
52
+ end
53
+ Evolve = Data.define(:state, :event) do
54
+ def self.build(state, event)
55
+ new(state: state, event: event)
56
+ end
57
+ end
58
+ Terminal = Data.define(:state)
59
+
60
+ def initialize(initial_state:, deciders:, evolutions:, terminal:)
61
+ define_method(:initial_state) do
62
+ initial_state
63
+ end
64
+
65
+ define_method(:decide) do |command, state|
66
+ context = Decide.new(command: command, state: state, _events: [])
67
+
68
+ deciders.find(DECIDE_FALLBACK) do |args, _|
69
+ case args
70
+ in [Proc => fn]
71
+ context.instance_exec(&fn)
72
+ in [ctype]
73
+ command in ^ctype
74
+ in [ctype, stype]
75
+ [command, state] in [^ctype, ^stype]
76
+ else
77
+ false
78
+ end
79
+ end => [_, handler]
80
+
81
+ context.instance_exec(&handler)
82
+ context._events
83
+ end
84
+
85
+ define_method(:evolve) do |*args|
86
+ if args.empty?
87
+ ->(state, event) { evolve(state, event) }
88
+ else
89
+ context = Evolve.build(*args)
90
+
91
+ evolutions.find(EVOLVE_FALLBACK) do |args, _|
92
+ case args
93
+ in [Proc => fn]
94
+ context.instance_exec(&fn)
95
+ in [etype]
96
+ context_event = context.event
97
+ context_event in ^etype
98
+ in [stype, etype]
99
+ context_state = context.state
100
+ context_event = context.event
101
+ [context_state, context_event] in [^stype, ^etype]
102
+ else
103
+ false
104
+ end
105
+ end => [_, handler]
106
+
107
+ context.instance_exec(&handler)
108
+ end
109
+ end
110
+
111
+ define_method(:terminal?) do |state|
112
+ context = Terminal.new(state: state)
113
+
114
+ context.instance_exec(&terminal)
115
+ end
116
+
117
+ define_method(:lmap_on_command) do |fn|
118
+ Decider.lmap_on_command(fn, self)
119
+ end
120
+
121
+ define_method(:lmap_on_state) do |fn|
122
+ Decider.lmap_on_state(fn, self)
123
+ end
124
+
125
+ define_method(:map) do |fn|
126
+ Decider.map(fn, self)
127
+ end
128
+
129
+ define_method(:rmap_on_state) do |fn|
130
+ Decider.rmap_on_state(fn, self)
131
+ end
132
+
133
+ define_method(:dimap_on_state) do |fl:, fr:|
134
+ Decider.dimap_on_state(fl, fr, self)
135
+ end
136
+
137
+ define_method(:lmap_on_event) do |fn|
138
+ Decider.lmap_on_event(fn, self)
139
+ end
140
+
141
+ define_method(:rmap_on_event) do |fn|
142
+ Decider.rmap_on_event(fn, self)
143
+ end
144
+
145
+ define_method(:dimap_on_event) do |fl:, fr:|
146
+ Decider.dimap_on_event(fl, fr, self)
147
+ end
148
+
149
+ define_method(:many) do
150
+ Decider.many(self)
151
+ end
152
+
153
+ define_method(:apply) do |f|
154
+ Decider.apply(self, f)
155
+ end
156
+ end
157
+ end
158
+
159
+ class Builder
160
+ DEFAULT = Object.new
161
+
162
+ def initialize
163
+ @initial_state = DEFAULT
164
+ @deciders = {}
165
+ @evolutions = {}
166
+ @terminal = proc { false }
167
+ end
168
+
169
+ def build(&block)
170
+ instance_exec(&block) if block_given?
171
+
172
+ raise StateNotDefined if @initial_state == DEFAULT
173
+
174
+ decider = Class.new
175
+
176
+ mod = Module.new(
177
+ initial_state: @initial_state,
178
+ deciders: deciders,
179
+ evolutions: evolutions,
180
+ terminal: terminal
181
+ )
182
+
183
+ decider.extend(mod)
184
+
185
+ decider
186
+ end
187
+
188
+ private
189
+
190
+ attr_reader :deciders, :evolutions, :terminal
191
+
192
+ def initial_state(state)
193
+ raise StateAlreadyDefined if @initial_state != DEFAULT
194
+
195
+ @initial_state = state
196
+ end
197
+
198
+ def decide(*args, &block)
199
+ deciders[args] = block
200
+ end
201
+
202
+ def evolve(*args, &block)
203
+ evolutions[args] = block
204
+ end
205
+
206
+ def terminal?(&block)
207
+ @terminal = block
208
+ end
209
+ end
210
+ private_constant :Builder
211
+
212
+ def self.define(&block)
213
+ builder = Builder.new
214
+ builder.build(&block)
215
+ end
216
+
217
+ def self.compose(left, right)
218
+ define do
219
+ initial_state Pair.new(
220
+ left: left.initial_state,
221
+ right: right.initial_state
222
+ )
223
+
224
+ decide proc { command in [:left, _] } do
225
+ left.decide(command.value, state.left).each { emit Left.new(_1) }
226
+ end
227
+
228
+ decide proc { command in [:right, _] } do
229
+ right.decide(command.value, state.right).each { emit Right.new(_1) }
230
+ end
231
+
232
+ evolve proc { event in [:left, _] } do
233
+ state.with(
234
+ left: left.evolve(state.left, event.value)
235
+ )
236
+ end
237
+
238
+ evolve proc { event in [:right, _] } do
239
+ state.with(
240
+ right: right.evolve(state.right, event.value)
241
+ )
242
+ end
243
+
244
+ terminal? do
245
+ left.terminal?(state.left) && right.terminal?(state.right)
246
+ end
247
+ end
248
+ end
249
+
250
+ def self.lmap_on_command(fn, decider)
251
+ define do
252
+ initial_state decider.initial_state
253
+
254
+ decide proc { true } do
255
+ decider.decide(fn.call(command), state).each(&method(:emit))
256
+ end
257
+
258
+ evolve proc { true } do
259
+ decider.evolve(state, event)
260
+ end
261
+
262
+ terminal? do
263
+ decider.terminal?(state)
264
+ end
265
+ end
266
+ end
267
+
268
+ def self.lmap_on_state(fn, decider)
269
+ dimap_on_state(fn, ->(state) { state }, decider)
270
+ end
271
+
272
+ def self.map(fn, decider)
273
+ dimap_on_state(->(state) { state }, fn, decider)
274
+ end
275
+
276
+ def self.rmap_on_state(fn, decider)
277
+ dimap_on_state(->(state) { state }, fn, decider)
278
+ end
279
+
280
+ def self.dimap_on_state(fl, fr, decider)
281
+ define do
282
+ initial_state fr.call(decider.initial_state)
283
+
284
+ decide proc { true } do
285
+ decider.decide(command, fl.call(state)).each(&method(:emit))
286
+ end
287
+
288
+ evolve proc { true } do
289
+ fr.call(decider.evolve(fl.call(state), event))
290
+ end
291
+
292
+ terminal? do
293
+ decider.terminal?(fl.call(state))
294
+ end
295
+ end
296
+ end
297
+
298
+ def self.lmap_on_event(fn, decider)
299
+ dimap_on_event(fn, ->(event) { event }, decider)
300
+ end
301
+
302
+ def self.rmap_on_event(fn, decider)
303
+ dimap_on_event(->(event) { event }, fn, decider)
304
+ end
305
+
306
+ def self.dimap_on_event(fl, fr, decider)
307
+ define do
308
+ initial_state decider.initial_state
309
+
310
+ decide proc { true } do
311
+ decider.decide(command, state).each do |event|
312
+ emit fr.call(event)
313
+ end
314
+ end
315
+
316
+ evolve proc { true } do
317
+ decider.evolve(state, fl.call(event))
318
+ end
319
+
320
+ terminal? do
321
+ decider.terminal?(state)
322
+ end
323
+ end
324
+ end
325
+
326
+ def self.many(decider)
327
+ define do
328
+ initial_state({})
329
+
330
+ decide proc { [command, state] in [[_id, _], Hash] } do
331
+ command => [id, command]
332
+
333
+ ds = state.fetch(id) { decider.initial_state }
334
+
335
+ decider.decide(command, ds).each do |event|
336
+ emit [id, event]
337
+ end
338
+ end
339
+
340
+ evolve proc { [state, event] in [Hash, [_id, _]] } do
341
+ event => [id, event]
342
+
343
+ ds = state.fetch(id) { decider.initial_state }
344
+ ds = decider.evolve(ds, event)
345
+
346
+ state.merge(id => ds)
347
+ end
348
+
349
+ terminal? do
350
+ state.any? && state.all? { |_, ds| decider.terminal?(ds) }
351
+ end
352
+ end
353
+ end
354
+
355
+ def self.apply(f, x)
356
+ map2(->(f, x) { f.call(x) }, f, x)
357
+ end
358
+
359
+ def self.map2(fn, dx, dy)
360
+ define do
361
+ initial_state fn.call(dx.initial_state, dy.initial_state)
362
+
363
+ decide proc { true } do
364
+ dx.decide(command, state).each(&method(:emit))
365
+ dy.decide(command, state).each(&method(:emit))
366
+ end
367
+
368
+ evolve proc { true } do
369
+ fn.call(
370
+ dx.evolve(state, event),
371
+ dy.evolve(state, event)
372
+ )
373
+ end
374
+
375
+ terminal? do
376
+ dx.terminal?(state) && dy.terminal?(state)
377
+ end
378
+ end
379
+ end
380
+ end
data/mise.toml ADDED
@@ -0,0 +1,17 @@
1
+ [env]
2
+ _.path = ["bin"]
3
+
4
+ [tools]
5
+ ruby = "4.0.2"
6
+
7
+ [tasks.test]
8
+ run = "bundle exec rake"
9
+ alias = "t"
10
+
11
+ [tasks.console]
12
+ run = "console"
13
+ alias = "c"
14
+
15
+ [tasks.release]
16
+ run = "bundle exec rake release"
17
+ alias = "r"
data/sig/decider.rbs ADDED
@@ -0,0 +1,16 @@
1
+ module Decider
2
+ VERSION: String
3
+
4
+ interface _Decider[C, S, E]
5
+ def decide: (C, S) -> Array[E]
6
+
7
+ def evolve: (S, E) -> S
8
+
9
+ def initial_state: () -> S
10
+
11
+ def terminal?: (S) -> bool
12
+ end
13
+
14
+ def self.compose: [C1, S1, E1, C2, S2, E2] (_Decider[C1, S1, E1], _Decider[C2, S2, E2]) -> _Decider[C1 | C2, S1 & S2, E1 | E2]
15
+ def self.define: [C, S, E] () -> _Decider[C, S, E]
16
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Dudulski
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: sqlite3
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.5.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.5.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: ruby_event_store
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.15'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.15'
54
+ description: Functional Event Sourcing Decider in Ruby
55
+ email:
56
+ - jan@dudulski.pl
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".standard.yml"
62
+ - CHANGELOG.md
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - Steepfile
67
+ - examples/decide.md
68
+ - examples/evolve.md
69
+ - examples/infra.md
70
+ - examples/state.md
71
+ - lib/decider.rb
72
+ - lib/decider/event_sourcing.rb
73
+ - lib/decider/in_memory.rb
74
+ - lib/decider/reactor.rb
75
+ - lib/decider/state.rb
76
+ - lib/decider/version.rb
77
+ - lib/decider/view.rb
78
+ - mise.toml
79
+ - sig/decider.rbs
80
+ homepage: https://github.com/jandudulski/decider
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ allowed_push_host: https://rubygems.org
85
+ bug_tracker_uri: https://github.com/jandudulski/decider/issues
86
+ changelog_uri: https://github.com/jandudulski/decider/CHANGELOG.md
87
+ documentation_uri: https://github.com/jandudulski/decider
88
+ homepage_uri: https://github.com/jandudulski/decider
89
+ source_code_uri: https://github.com/jandudulski/decider
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 3.0.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 4.0.6
105
+ specification_version: 4
106
+ summary: Functional Event Sourcing Decider in Ruby
107
+ test_files: []