dry-effects 0.1.0.alpha2 → 0.1.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -4
  3. data/examples/{amb.rb → cmp.rb} +8 -8
  4. data/lib/dry/effects.rb +33 -2
  5. data/lib/dry/effects/all.rb +3 -2
  6. data/lib/dry/effects/constructors.rb +4 -0
  7. data/lib/dry/effects/effects/{amb.rb → cmp.rb} +3 -3
  8. data/lib/dry/effects/effects/current_time.rb +9 -0
  9. data/lib/dry/effects/effects/resolve.rb +1 -3
  10. data/lib/dry/effects/effects/retry.rb +7 -2
  11. data/lib/dry/effects/effects/state.rb +12 -3
  12. data/lib/dry/effects/effects/timeout.rb +31 -0
  13. data/lib/dry/effects/errors.rb +19 -0
  14. data/lib/dry/effects/frame.rb +80 -0
  15. data/lib/dry/effects/handler.rb +11 -44
  16. data/lib/dry/effects/provider.rb +19 -0
  17. data/lib/dry/effects/provider/class_interface.rb +4 -3
  18. data/lib/dry/effects/providers/async.rb +4 -1
  19. data/lib/dry/effects/providers/cache.rb +8 -0
  20. data/lib/dry/effects/providers/cmp.rb +50 -0
  21. data/lib/dry/effects/providers/current_time.rb +38 -21
  22. data/lib/dry/effects/providers/defer.rb +8 -3
  23. data/lib/dry/effects/providers/env.rb +10 -0
  24. data/lib/dry/effects/providers/fork.rb +1 -1
  25. data/lib/dry/effects/providers/implicit.rb +6 -0
  26. data/lib/dry/effects/providers/interrupt.rb +10 -2
  27. data/lib/dry/effects/providers/lock.rb +7 -0
  28. data/lib/dry/effects/providers/parallel.rb +4 -1
  29. data/lib/dry/effects/providers/reader.rb +8 -1
  30. data/lib/dry/effects/providers/resolve.rb +17 -0
  31. data/lib/dry/effects/providers/retry.rb +6 -1
  32. data/lib/dry/effects/providers/state.rb +6 -0
  33. data/lib/dry/effects/providers/timeout.rb +47 -0
  34. data/lib/dry/effects/providers/timestamp.rb +27 -16
  35. data/lib/dry/effects/version.rb +1 -1
  36. metadata +11 -8
  37. data/lib/dry/effects/providers/amb.rb +0 -36
@@ -1,58 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fiber'
4
- require 'dry/effects/initializer'
5
- require 'dry/effects/effect'
6
- require 'dry/effects/errors'
7
- require 'dry/effects/stack'
8
- require 'dry/effects/instructions/raise'
9
-
10
3
  module Dry
11
4
  module Effects
12
5
  class Handler
13
- class << self
14
- def stack
15
- ::Thread.current[:dry_effects_stack] ||= Stack.new
16
- end
17
-
18
- def stack=(stack)
19
- ::Thread.current[:dry_effects_stack] = stack
20
- end
21
-
22
- def spawn_fiber(stack)
23
- fiber = ::Fiber.new do
24
- self.stack = stack
25
- yield
26
- end
27
- result = fiber.resume
28
-
29
- loop do
30
- break result unless fiber.alive?
6
+ attr_reader :provider
31
7
 
32
- provided = stack.(result) do
33
- ::Dry::Effects.yield(result) do |_, error|
34
- Instructions.Raise(error)
35
- end
36
- end
8
+ attr_reader :frame
37
9
 
38
- result = fiber.resume(provided)
39
- end
40
- end
10
+ def initialize(type, *args)
11
+ @provider = ::Dry::Effects.providers[type].new(*args)
12
+ @frame = Frame.new(provider)
41
13
  end
42
14
 
43
- extend Initializer
44
-
45
- param :provider
46
-
47
- def call(args = EMPTY_ARRAY, &block)
48
- stack = Handler.stack
15
+ def call(*args, &block)
16
+ frame.(args, &block)
17
+ end
49
18
 
50
- if stack.empty?
51
- stack.push(provider.dup, args) { Handler.spawn_fiber(stack, &block) }
52
- else
53
- stack.push(provider.dup, args, &block)
54
- end
19
+ def to_s
20
+ "#<Dry::Effects::Handler #{provider.represent}>"
55
21
  end
22
+ alias_method :inspect, :to_s
56
23
  end
57
24
  end
58
25
  end
@@ -5,22 +5,41 @@ require 'dry/effects/provider/class_interface'
5
5
 
6
6
  module Dry
7
7
  module Effects
8
+ # Base class for effect providers
9
+ #
10
+ # @api private
8
11
  class Provider
9
12
  extend Initializer
10
13
  extend ClassInterface
11
14
 
15
+ # yield the block with the handler installed
16
+ #
17
+ # @api private
12
18
  def call(_stack)
13
19
  yield
14
20
  end
15
21
 
22
+ # Effect-specific representation of the provider
23
+ #
24
+ # @return [String]
25
+ # @api public
16
26
  def represent
17
27
  type.to_s
18
28
  end
19
29
 
30
+ # Effect type
31
+ #
32
+ # @return [Symbol]
33
+ # @api public
20
34
  def type
21
35
  self.class.type
22
36
  end
23
37
 
38
+ # Whether the effect can be handled?
39
+ #
40
+ # @param [Effect] effect
41
+ # @return [Boolean]
42
+ # @api public
24
43
  def provide?(effect)
25
44
  type.equal?(effect.type)
26
45
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/core/class_attributes'
4
+ require 'dry/effects/frame'
4
5
 
5
6
  module Dry
6
7
  module Effects
@@ -30,7 +31,7 @@ module Dry
30
31
  def [](type)
31
32
  if self < Provider
32
33
  Provider.effects.fetch(type) do
33
- Provider.effects[type] = Class.new(self).tap do |subclass|
34
+ Provider.effects[type] = ::Class.new(self).tap do |subclass|
34
35
  subclass.type type
35
36
  end
36
37
  end
@@ -43,11 +44,11 @@ module Dry
43
44
  handle_method = handle_method(*args, **kwargs)
44
45
 
45
46
  provider = new(*args, **kwargs).freeze
46
- handler = Handler.new(provider)
47
+ frame = Frame.new(provider)
47
48
 
48
49
  ::Module.new do
49
50
  define_method(handle_method) do |*xs, &block|
50
- handler.(xs, &block)
51
+ frame.(xs, &block)
51
52
  end
52
53
  end
53
54
  end
@@ -17,9 +17,12 @@ module Dry
17
17
  end
18
18
 
19
19
  def await(task)
20
- Handler.spawn_fiber(stack, &@tasks.delete(task))
20
+ Frame.spawn_fiber(stack, &@tasks.delete(task))
21
21
  end
22
22
 
23
+ # Yield the block with the handler installed
24
+ #
25
+ # @api private
23
26
  def call(stack)
24
27
  @stack = stack
25
28
  super
@@ -21,15 +21,23 @@ module Dry
21
21
  end
22
22
  end
23
23
 
24
+ # Yield the block with the handler installed
25
+ #
26
+ # @api private
24
27
  def call(stack, cache = EMPTY_HASH.dup)
25
28
  @cache = cache
26
29
  super(stack)
27
30
  end
28
31
 
32
+ # @param [Effect] effect
33
+ # @return [Boolean]
34
+ # @api public
29
35
  def provide?(effect)
30
36
  super && scope.eql?(effect.scope)
31
37
  end
32
38
 
39
+ # @return [String]
40
+ # @api public
33
41
  def represent
34
42
  if cache.empty?
35
43
  "cache[#{scope} empty]"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/effects/provider'
4
+
5
+ module Dry
6
+ module Effects
7
+ module Providers
8
+ class Cmp < Provider[:cmp]
9
+ include Dry::Equalizer(:id, :value)
10
+
11
+ attr_reader :value
12
+
13
+ param :id
14
+
15
+ def get
16
+ value
17
+ end
18
+
19
+ # Yield the block with the handler installed
20
+ #
21
+ # @return [Array(Any, Any)]
22
+ # @api private
23
+ def call(stack, value = Undefined)
24
+ if Undefined.equal?(value)
25
+ @value = false
26
+ first = super(stack)
27
+ @value = true
28
+ [first, super(stack)]
29
+ else
30
+ @value = value
31
+ super(stack)
32
+ end
33
+ end
34
+
35
+ # @param [Effect] effect
36
+ # @return [Boolean]
37
+ # @api public
38
+ def provide?(effect)
39
+ super && id.equal?(effect.id)
40
+ end
41
+
42
+ # @return [String]
43
+ # @api public
44
+ def represent
45
+ "cmp[#{id}=#{@value}]"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -20,31 +20,14 @@ module Dry
20
20
 
21
21
  attr_reader :generator
22
22
 
23
+ # Yield the block with the handler installed
24
+ #
25
+ # @api private
23
26
  def call(stack, generator = Undefined, **options)
24
27
  @generator = build_generator(generator, **options)
25
28
  super(stack)
26
29
  end
27
30
 
28
- def build_generator(generator, step: Undefined, initial: Undefined, overridable: false)
29
- if overridable
30
- parent = ::Dry::Effects.yield(Locate) { nil }
31
- else
32
- parent = nil
33
- end
34
-
35
- if !parent.nil?
36
- -> options { parent.current_time(options) }
37
- elsif !Undefined.equal?(generator)
38
- generator
39
- elsif !Undefined.equal?(step)
40
- IncrementingTimeGenerator.(initial, step)
41
- elsif fixed?
42
- FixedTimeGenerator.()
43
- else
44
- RunningTimeGenerator.()
45
- end
46
- end
47
-
48
31
  def current_time(round_to: Undefined, **options)
49
32
  time = generator.(**options)
50
33
 
@@ -57,17 +40,51 @@ module Dry
57
40
  end
58
41
  end
59
42
 
43
+ # Locate handler in the stack
44
+ #
45
+ # @return [Provider]
46
+ # @api private
60
47
  def locate
61
48
  self
62
49
  end
63
50
 
51
+ # @return [String]
52
+ # @api public
64
53
  def represent
65
54
  if fixed?
66
- "current_time[fixed=#{generator.().iso8601(6)}]"
55
+ if generator.nil?
56
+ 'current_time[fixed=true]'
57
+ else
58
+ "current_time[fixed=#{generator.().iso8601(6)}]"
59
+ end
67
60
  else
68
61
  'current_time[fixed=false]'
69
62
  end
70
63
  end
64
+
65
+ private
66
+
67
+ # @return [Proc] time generator
68
+ # @api private
69
+ def build_generator(generator, step: Undefined, initial: Undefined, overridable: false)
70
+ if overridable
71
+ parent = ::Dry::Effects.yield(Locate) { nil }
72
+ else
73
+ parent = nil
74
+ end
75
+
76
+ if !parent.nil?
77
+ -> **options { parent.current_time(**options) }
78
+ elsif !Undefined.equal?(generator)
79
+ generator
80
+ elsif !Undefined.equal?(step)
81
+ IncrementingTimeGenerator.(initial, step)
82
+ elsif fixed?
83
+ FixedTimeGenerator.()
84
+ else
85
+ RunningTimeGenerator.()
86
+ end
87
+ end
71
88
  end
72
89
  end
73
90
  end
@@ -19,21 +19,21 @@ module Dry
19
19
  stack = self.stack.dup
20
20
  at = Undefined.default(executor, self.executor)
21
21
  ::Concurrent::Promise.execute(executor: at) do
22
- Handler.spawn_fiber(stack, &block)
22
+ Frame.spawn_fiber(stack, &block)
23
23
  end
24
24
  end
25
25
 
26
26
  def later(block, executor)
27
27
  if @later_calls.frozen?
28
28
  Instructions.Raise(Errors::EffectRejectedError.new(<<~MSG))
29
- .later calls are not allowed, they would processed
29
+ .later calls are not allowed, they would be processed
30
30
  by another stack. Add another defer handler to the current stack
31
31
  MSG
32
32
  else
33
33
  at = Undefined.default(executor, self.executor)
34
34
  stack = self.stack.dup
35
35
  @later_calls << ::Concurrent::Promise.new(executor: at) do
36
- Handler.spawn_fiber(stack, &block)
36
+ Frame.spawn_fiber(stack, &block)
37
37
  end
38
38
  nil
39
39
  end
@@ -47,6 +47,9 @@ module Dry
47
47
  end
48
48
  end
49
49
 
50
+ # Yield the block with the handler installed
51
+ #
52
+ # @api private
50
53
  def call(stack, executor: Undefined)
51
54
  unless Undefined.equal?(executor)
52
55
  @executor = executor
@@ -67,6 +70,8 @@ module Dry
67
70
  end
68
71
  end
69
72
 
73
+ # @return [String]
74
+ # @api public
70
75
  def represent
71
76
  info = []
72
77
  info << executor.to_s if executor.is_a?(::Symbol)
@@ -30,10 +30,17 @@ module Dry
30
30
  end
31
31
  protected :fetch
32
32
 
33
+ # Locate handler in the stack
34
+ #
35
+ # @return [Provider]
36
+ # @api private
33
37
  def locate
34
38
  self
35
39
  end
36
40
 
41
+ # Yield the block with the handler installed
42
+ #
43
+ # @api private
37
44
  def call(stack, values = EMPTY_HASH, options = EMPTY_HASH)
38
45
  unless values.empty?
39
46
  @values = @values.merge(values)
@@ -48,6 +55,9 @@ module Dry
48
55
  super(stack)
49
56
  end
50
57
 
58
+ # @param [Effect] effect
59
+ # @return [Boolean]
60
+ # @api public
51
61
  def provide?(effect)
52
62
  if super
53
63
  !effect.name.equal?(:read) || key?(effect.payload[0])
@@ -10,7 +10,7 @@ module Dry
10
10
 
11
11
  def fork
12
12
  stack = self.stack.dup
13
- -> &cont { Handler.spawn_fiber(stack.dup, &cont) }
13
+ -> &cont { Frame.spawn_fiber(stack.dup, &cont) }
14
14
  end
15
15
 
16
16
  def call(stack)
@@ -18,6 +18,9 @@ module Dry
18
18
  dictionary.fetch(arg.class)
19
19
  end
20
20
 
21
+ # Yield the block with the handler installed
22
+ #
23
+ # @api private
21
24
  def call(stack, dynamic = EMPTY_HASH)
22
25
  if dynamic.empty?
23
26
  @dictionary = static
@@ -28,6 +31,9 @@ module Dry
28
31
  super(stack)
29
32
  end
30
33
 
34
+ # @param [Effect] effect
35
+ # @return [Boolean]
36
+ # @api public
31
37
  def provide?(effect)
32
38
  super &&
33
39
  dependency.equal?(effect.dependency) &&
@@ -14,20 +14,28 @@ module Dry
14
14
  Instructions.Raise(halt.new(payload))
15
15
  end
16
16
 
17
+ # Yield the block with the handler installed
18
+ #
19
+ # @api private
17
20
  def call(_stack)
18
- yield
21
+ [false, yield]
19
22
  rescue halt => e
20
- e.payload[0]
23
+ [true, e.payload[0]]
21
24
  end
22
25
 
23
26
  def halt
24
27
  Halt[scope]
25
28
  end
26
29
 
30
+ # @return [String]
31
+ # @api public
27
32
  def represent
28
33
  "interrupt[#{scope}]"
29
34
  end
30
35
 
36
+ # @param [Effect] effect
37
+ # @return [Boolean]
38
+ # @api public
31
39
  def provide?(effect)
32
40
  super && scope.equal?(effect.scope)
33
41
  end