dry-effects 0.1.0.alpha2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e467f4de9697568b05a68ccc58c3d7acc44ab3f196e6a93c07c574b6aaeca6f9
4
- data.tar.gz: 4f3b5093c7aabb77aeab310080bc1a1e2091537da7724c5c1b70587cbcbce12b
3
+ metadata.gz: b4b516150a36745c1fac9a030dc079a629737b0a79b35b5e6901c413e7019479
4
+ data.tar.gz: 0f81389b62e44b1196535332407cec3d894ccc7f6413f97e32766d0b5109426f
5
5
  SHA512:
6
- metadata.gz: bc7202bfd43a789fd1221323126eee3000479c45508ccb08874a29b91b86ee089a6248b7f33a64a651aa296b84a4ae425bcdab689b18709144ea33ab3cd9e582
7
- data.tar.gz: 1e2a61bcfe1e64991317b028b1bae6e3ddd663c3b658c9f67ecb14dc0570610d75bccc94ad8ec9cb7d8f935099086dadcb6e075bc702a920820fc07b90bb8571
6
+ metadata.gz: 97c935e9b3b37f026c74b1e5e055f43f17b718dad48095b80f730c610d685990a0e9d1cd473859154d78fa388f44ae211f861ec066ec268b95b58d1543b2d07b
7
+ data.tar.gz: dc49f0fd42b63ec247322a177e73334a30f336e70b904c03c9708b2991dd85344b9b6f62833b768501082434b49fc69060980111ca0a137c922b66186d27d7a9
@@ -7,9 +7,9 @@ before_script:
7
7
  - chmod +x ./cc-test-reporter
8
8
  - ./cc-test-reporter before-build
9
9
  rvm:
10
- - 2.4.6
11
- - 2.5.5
12
- - 2.6.3
10
+ - 2.4.7
11
+ - 2.5.6
12
+ - 2.6.4
13
13
  - truffleruby
14
14
  after_script:
15
15
  - "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
@@ -20,7 +20,7 @@ matrix:
20
20
  allow_failures:
21
21
  - rvm: truffleruby
22
22
  include:
23
- - rvm: jruby-9.2.7.0
23
+ - rvm: jruby-9.2.8.0
24
24
  jdk: openjdk8
25
25
  notifications:
26
26
  email: false
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Operation
4
4
  include Dry::Effects.State(:counter)
5
- include Dry::Effects.Amb(:feature_enabled)
5
+ include Dry::Effects.Cmp(:feature_enabled)
6
6
 
7
7
  def call
8
8
  if feature_enabled?
@@ -17,10 +17,10 @@ end
17
17
 
18
18
  module Handler
19
19
  include Dry::Effects::Handler.State(:counter, as: :with_counter)
20
- include Dry::Effects::Handler.Amb(:feature_enabled, as: :test_feature)
20
+ include Dry::Effects::Handler.Cmp(:feature_enabled, as: :test_feature)
21
21
  end
22
22
 
23
- class AmbState
23
+ class CmpState
24
24
  include Handler
25
25
 
26
26
  def initialize
@@ -32,7 +32,7 @@ class AmbState
32
32
  end
33
33
  end
34
34
 
35
- class StateAmb
35
+ class StateCmp
36
36
  include Handler
37
37
 
38
38
  def initialize
@@ -44,8 +44,8 @@ class StateAmb
44
44
  end
45
45
  end
46
46
 
47
- amb_then_state = AmbState.new
48
- state_then_amb = StateAmb.new
47
+ cmp_then_state = CmpState.new
48
+ state_then_cmp = State.Cmp.new
49
49
 
50
- amb_then_state.() # => [[1, :without_feature], [10, :with_feature]]
51
- state_then_amb.() # => [11, [:without_feature, :with_feature]]
50
+ cmp_then_state.() # => [[1, :without_feature], [10, :with_feature]]
51
+ state_then_cmp.() # => [11, [:without_feature, :with_feature]]
@@ -18,6 +18,21 @@ module Dry
18
18
  class << self
19
19
  attr_reader :effects, :providers
20
20
 
21
+ # Handle an effect.
22
+ # If no handler is present in the stack it will either
23
+ # raise an exception and yield a block if given.
24
+ # It is not recommended to build effects manually, hence
25
+ # this method shouldn't be used often.
26
+ #
27
+ # @example getting current user with yield
28
+ #
29
+ # require 'dry/effects/effects/reader'
30
+ # extend Dry::Effects::Constructors
31
+ # Dry::Effects.yield(Read(:current_user))
32
+ #
33
+ # @param [Effect] effect
34
+ # @return [Object] Result value is determined by effect type
35
+ # @api public
21
36
  def yield(effect)
22
37
  result = ::Fiber.yield(effect)
23
38
 
@@ -26,17 +41,33 @@ module Dry
26
41
  else
27
42
  result
28
43
  end
29
- rescue FiberError => e
44
+ rescue ::FiberError => e
30
45
  if block_given?
31
46
  yield(effect, e)
32
47
  else
33
48
  raise Errors::UnhandledEffectError, effect
34
49
  end
35
50
  end
51
+
52
+ # Build a handler.
53
+ # Normally, handlers are built via mixins.
54
+ # This method is useful for demonstration purposes.
55
+ #
56
+ # @example providing current user
57
+ #
58
+ # Dry::Effects[:reader, :current_user].(User.new) do
59
+ # code_using_current_user.()
60
+ # end
61
+ #
62
+ # @param [Array<Object>] args Handler parameters
63
+ # @return [Handler]
64
+ # @api public
65
+ def [](*args)
66
+ Handler.new(*args)
67
+ end
36
68
  end
37
69
  end
38
70
  end
39
71
 
40
- require 'dry/effects/handler'
41
72
  require 'dry/effects/all'
42
73
  require 'dry/effects/extensions'
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'concurrent/map'
4
- require 'dry/effects'
5
4
  require 'dry/effects/inflector'
5
+ require 'dry/effects/handler'
6
6
 
7
7
  module Dry
8
8
  module Effects
9
9
  default = %i[
10
10
  cache current_time random resolve defer
11
- state interrupt amb retry fork parallel
11
+ state interrupt cmp retry fork parallel
12
12
  async implicit env lock reader timestamp
13
+ timeout
13
14
  ]
14
15
 
15
16
  effect_modules = ::Concurrent::Map.new
@@ -3,6 +3,10 @@
3
3
  module Dry
4
4
  module Effects
5
5
  module Constructors
6
+ def self.register(name, &block)
7
+ define_method(name, &block)
8
+ module_function name
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -5,13 +5,13 @@ require 'dry/effects/effect'
5
5
  module Dry
6
6
  module Effects
7
7
  module Effects
8
- class Amb < ::Module
9
- class AmbEffect < Effect
8
+ class Cmp < ::Module
9
+ class CmpEffect < Effect
10
10
  option :id
11
11
  end
12
12
 
13
13
  def initialize(id)
14
- get = AmbEffect.new(type: :amb, name: :get, id: id)
14
+ get = CmpEffect.new(type: :cmp, name: :get, id: id)
15
15
 
16
16
  module_eval do
17
17
  define_method(:"#{id}?") { ::Dry::Effects.yield(get) }
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/effects/effect'
4
+ require 'dry/effects/constructors'
4
5
 
5
6
  module Dry
6
7
  module Effects
@@ -8,6 +9,14 @@ module Dry
8
9
  class CurrentTime < ::Module
9
10
  CurrentTime = Effect.new(type: :current_time)
10
11
 
12
+ Constructors.register(:CurrentTime) do |**kwargs|
13
+ if kwargs.empty?
14
+ CurrentTime
15
+ else
16
+ CurrentTime.payload(kwargs)
17
+ end
18
+ end
19
+
11
20
  def initialize(options = EMPTY_HASH)
12
21
  module_eval do
13
22
  define_method(:current_time) do |round: Undefined, refresh: false|
@@ -9,9 +9,7 @@ module Dry
9
9
  class Resolve < ::Module
10
10
  Resolve = Effect.new(type: :resolve)
11
11
 
12
- def Constructors.Resolve(key)
13
- Resolve.(key)
14
- end
12
+ Constructors.register(:Resolve) { |key| Resolve.(key) }
15
13
 
16
14
  def initialize(*keys, **aliases)
17
15
  module_eval do
@@ -1,21 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/effects/effect'
4
+ require 'dry/effects/constructors'
4
5
 
5
6
  module Dry
6
7
  module Effects
7
8
  module Effects
8
9
  class Retry < ::Module
9
- class RetryEffect < Effect
10
+ class Retry < Effect
10
11
  include ::Dry::Equalizer(:type, :name, :payload, :scope)
11
12
 
12
13
  option :scope
13
14
  end
14
15
 
16
+ Constructors.register(:Retry) do |scope|
17
+ Retry.new(type: :retry, scope: scope)
18
+ end
19
+
15
20
  def initialize
16
21
  module_eval do
17
22
  define_method(:repeat) do |scope|
18
- effect = RetryEffect.new(type: :retry, name: :repeat, scope: scope)
23
+ effect = Retry.new(type: :retry, scope: scope)
19
24
  ::Dry::Effects.yield(effect)
20
25
  end
21
26
  end
@@ -1,20 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'dry/effects/effect'
4
+ require 'dry/effects/constructors'
4
5
 
5
6
  module Dry
6
7
  module Effects
7
8
  module Effects
8
9
  class State < ::Module
9
- class StateEffect < Effect
10
+ class State < Effect
10
11
  include ::Dry::Equalizer(:type, :name, :payload, :scope)
11
12
 
12
13
  option :scope
13
14
  end
14
15
 
16
+ Constructors.register(:Read) do |scope|
17
+ State.new(type: :state, name: :read, scope: scope)
18
+ end
19
+
20
+ Constructors.register(:Write) do |scope, value|
21
+ State.new(type: :state, name: :write, scope: scope, payload: [value])
22
+ end
23
+
15
24
  def initialize(scope, default: Undefined, writer: true, as: scope)
16
- read = StateEffect.new(type: :state, name: :read, scope: scope)
17
- write = StateEffect.new(type: :state, name: :write, scope: scope)
25
+ read = State.new(type: :state, name: :read, scope: scope)
26
+ write = State.new(type: :state, name: :write, scope: scope)
18
27
 
19
28
  module_eval do
20
29
  if Undefined.equal?(default)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/effects/effect'
4
+
5
+ module Dry
6
+ module Effects
7
+ module Effects
8
+ class Timeout < ::Module
9
+ class TimeoutEffect < Effect
10
+ include ::Dry::Equalizer(:type, :name, :scope)
11
+
12
+ option :scope
13
+ end
14
+
15
+ def initialize(scope)
16
+ timeout = TimeoutEffect.new(type: :timeout, name: :timeout, scope: scope)
17
+
18
+ module_eval do
19
+ define_method(:timeout) do
20
+ ::Dry::Effects.yield(timeout)
21
+ end
22
+
23
+ def timed_out?
24
+ timeout.zero?
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,6 +6,9 @@ module Dry
6
6
  module Error
7
7
  end
8
8
 
9
+ # No handler in the stack
10
+ #
11
+ # @api private
9
12
  class UnhandledEffectError < RuntimeError
10
13
  include Error
11
14
 
@@ -23,6 +26,9 @@ module Dry
23
26
  end
24
27
  end
25
28
 
29
+ # No state handler
30
+ #
31
+ # @api private
26
32
  class MissingStateError < UnhandledEffectError
27
33
  def initialize(effect)
28
34
  message = "Value of +#{effect.scope}+ is not set, "\
@@ -32,6 +38,9 @@ module Dry
32
38
  end
33
39
  end
34
40
 
41
+ # Uninitialized state accessed
42
+ #
43
+ # @api private
35
44
  class UndefinedStateError < RuntimeError
36
45
  include Error
37
46
 
@@ -44,10 +53,17 @@ module Dry
44
53
  end
45
54
  end
46
55
 
56
+ # Effect cannot be handled
57
+ # Some effects are not compatible without re
58
+ #
59
+ # @api private
47
60
  class EffectRejectedError < RuntimeError
48
61
  include Error
49
62
  end
50
63
 
64
+ # Unresolved dependency
65
+ #
66
+ # @api private
51
67
  class ResolutionError < RuntimeError
52
68
  include Error
53
69
 
@@ -56,6 +72,9 @@ module Dry
56
72
  end
57
73
  end
58
74
 
75
+ # State value has invalid type
76
+ #
77
+ # @api private
59
78
  class InvalidValueError < ArgumentError
60
79
  include Error
61
80
 
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
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
+ module Dry
11
+ module Effects
12
+ # Stack frame
13
+ #
14
+ # @api private
15
+ class Frame
16
+ class << self
17
+ # Accessing current stack of effect handlers.
18
+ # It is inherently thread/fiber-local.
19
+ #
20
+ # @return [Stack]
21
+ # @api private
22
+ def stack
23
+ ::Thread.current[:dry_effects_stack] ||= Stack.new
24
+ end
25
+
26
+ # @param [Stack] stack
27
+ # @api private
28
+ def stack=(stack)
29
+ ::Thread.current[:dry_effects_stack] = stack
30
+ end
31
+
32
+ # Spawn a new fiber with a stack of effect handlers
33
+ #
34
+ # @param [Stack] stack
35
+ # @api private
36
+ def spawn_fiber(stack)
37
+ fiber = ::Fiber.new do
38
+ self.stack = stack
39
+ yield
40
+ end
41
+ result = fiber.resume
42
+
43
+ loop do
44
+ break result unless fiber.alive?
45
+
46
+ provided = stack.(result) do
47
+ ::Dry::Effects.yield(result) do |_, error|
48
+ Instructions.Raise(error)
49
+ end
50
+ end
51
+
52
+ result = fiber.resume(provided)
53
+ end
54
+ end
55
+ end
56
+
57
+ extend Initializer
58
+
59
+ # @!attribute provider
60
+ # @return [Provider] Effects provider
61
+ param :provider
62
+
63
+ # Add new handler to the current stack
64
+ # and run the given block
65
+ #
66
+ # @param [Array<Object>] args Handler arguments
67
+ # @param [Proc] block Program to run
68
+ # @api private
69
+ def call(args = EMPTY_ARRAY, &block)
70
+ stack = Frame.stack
71
+
72
+ if stack.empty?
73
+ stack.push(provider.dup, args) { Frame.spawn_fiber(stack, &block) }
74
+ else
75
+ stack.push(provider.dup, args, &block)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end