fear 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ab163e8a3ccc420c6c1436e42c269e36e333b36
4
- data.tar.gz: ac6d7a735f7c6ac3f20194438252b59fbdecf8c4
3
+ metadata.gz: 9cbecbc38d219215fb5b302a437db94936dd1d41
4
+ data.tar.gz: ed92b8831f89e7a31f7a91ab8a9e3992ab36dec0
5
5
  SHA512:
6
- metadata.gz: 8a8f2500d2c88ba91388420a4d8e2f29ccc8d912b77c5c75484e1ff472dd1ca4ec64dbd9dc537e116845972dab0659219dc2aea9f549494d5266129771c3bbc0
7
- data.tar.gz: a3c52d900667bd52580d2983cddafcb2e796524b19b418e4722fca99b520ce13232fc4ce18c433e01d653d1b5f3346ba5a27c7417114d807fe37b7ea16ac1db9
6
+ metadata.gz: d4390eccb7507ec87b1fe4d08e8fc86efe3274e9860a0b24ff1f5344f1512f018c8eb379fdf7a2fa3f97f0178dfad6b363dd7ac824ec570b211a21eb108bc2fc
7
+ data.tar.gz: 8437d3b4d64a69dbaaebb7a7a7d782df0966b10238cb8fe16c3e598ba47d196a3babf34447cfe51251900ce98bffa9c7048e7cfede886ef8a6d559a01c4bb5ab
@@ -0,0 +1,91 @@
1
+ module Fear
2
+ # This class provides syntactic sugar for composition of
3
+ # multiple monadic operations. It supports two such
4
+ # operations - +flat_map+ and +map+. Any class providing them
5
+ # is supported by +For+.
6
+ #
7
+ # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
8
+ #
9
+ # If one of operands is None, the result is None
10
+ #
11
+ # For(a: Some(2), b: None()) { a * b } #=> None()
12
+ # For(a: None(), b: Some(2)) { a * b } #=> None()
13
+ #
14
+ # Lets look at first example:
15
+ #
16
+ # For(a: Some(2), b: Some(3)) { a * b }
17
+ #
18
+ # would be translated to:
19
+ #
20
+ # Some(2).flat_map do |a|
21
+ # Some(3).map do |b|
22
+ # a * b
23
+ # end
24
+ # end
25
+ #
26
+ # It works with arrays as well
27
+ #
28
+ # For(a: [1, 2], b: [2, 3], c: [3, 4]) { a * b * c }
29
+ # #=> [6, 8, 9, 12, 12, 16, 18, 24]
30
+ #
31
+ # would be translated to:
32
+ #
33
+ # [1, 2].flat_map do |a|
34
+ # [2, 3].flat_map do |b|
35
+ # [3, 4].map do |c|
36
+ # a * b * c
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # If you pass lambda as a variable value, it would be evaluated
42
+ # only on demand.
43
+ #
44
+ # For(a: -> { None() }, b: -> { fail 'kaboom' } ) { a * b }
45
+ # #=> None()
46
+ #
47
+ # It does not fail since `b` is not evaluated.
48
+ # You can mix and match lambdas and variables.
49
+ #
50
+ # For(a: -> None(), b: -> { fail 'kaboom' } ) { a * b }
51
+ # #=> None()
52
+ #
53
+ class For
54
+ # Context of block evaluation. It respond to passed locals.
55
+ #
56
+ # @example
57
+ # context = EvaluationContext.new
58
+ # context.assign(:foo, 'bar')
59
+ # context.foo #=> 'bar'
60
+ #
61
+ class EvaluationContext < BasicObject
62
+ include ::Fear::Option::Mixin
63
+ include ::Fear::Either::Mixin
64
+ include ::Fear::Try::Mixin
65
+
66
+ def initialize(outer_context)
67
+ @assigns = {}
68
+ @outer_context = outer_context
69
+ end
70
+
71
+ def __assign__(name, value)
72
+ @assigns[name] = value
73
+ end
74
+
75
+ def method_missing(name, *args, &block)
76
+ if @assigns.include?(name) && args.empty? && block.nil?
77
+ @assigns[name]
78
+ elsif @outer_context.respond_to?(name)
79
+ @outer_context.__send__(name, *args, &block)
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ def respond_to_missing?(name, _)
86
+ @assigns.key?(name) && args.empty? && block.nil?
87
+ end
88
+ end
89
+ private_constant(:EvaluationContext)
90
+ end
91
+ end
data/lib/fear/for.rb CHANGED
@@ -38,68 +38,78 @@ module Fear
38
38
  # end
39
39
  # end
40
40
  #
41
+ # If you pass lambda as a variable value, it would be evaluated
42
+ # only on demand.
43
+ #
44
+ # For(a: -> { None() }, b: -> { fail 'kaboom' } ) { a * b }
45
+ # #=> None()
46
+ #
47
+ # It does not fail since `b` is not evaluated.
48
+ # You can refer to previously defined variables from within lambdas.
49
+ #
50
+ # maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
51
+ #
52
+ # For(user: maybe_user, birthday: -> { user.birthday }) do
53
+ # "#{user.name} was born on #{birthday}"
54
+ # end #=> Some('Paul was born on 1987-06-17')
55
+ #
41
56
  class For
42
- # Context of block evaluation. It respond to passed locals.
43
- #
44
- # @example
45
- # context = EvaluationContext.new(foo: 'bar')
46
- # context.foo #=> 'bar'
47
- #
48
- class EvaluationContext < BasicObject
49
- # @param locals [Hash{Symbol => any}]
50
- #
51
- def initialize(locals)
52
- @locals = locals
53
- end
54
-
55
- def method_missing(name, *args, &block)
56
- if @locals.include?(name) && args.empty? && block.nil?
57
- @locals[name]
58
- else
59
- super
60
- end
61
- end
62
-
63
- def respond_to_missing?(name, _)
64
- @locals.key?(name)
65
- end
66
- end
67
- private_constant(:EvaluationContext)
57
+ require_relative 'for/evaluation_context'
68
58
 
69
59
  # @param variables [Hash{Symbol => any}]
70
60
  #
71
- def initialize(**variables)
61
+ def initialize(outer_context, **variables)
72
62
  @variables = variables
63
+ @evaluation_context = EvaluationContext.new(outer_context)
73
64
  end
74
- attr_reader :variables
75
- private :variables
76
65
 
77
66
  def call(&block)
78
67
  variable_name_and_monad, *tail = *variables
79
- execute({}, *variable_name_and_monad, tail, &block)
68
+ execute(*variable_name_and_monad, tail, &block)
80
69
  end
81
70
 
82
71
  private
83
72
 
84
- def execute(locals, variable_name, monad, monads, &block)
73
+ attr_reader :variables
74
+ attr_reader :evaluation_context
75
+
76
+ def execute(variable_name, monad, monads, &block) # rubocop:disable Metrics/MethodLength
85
77
  if monads.empty?
86
- monad.map do |value|
87
- EvaluationContext.new(locals.merge(variable_name => value)).instance_eval(&block)
78
+ resolve(monad).map do |value|
79
+ evaluation_context.__assign__(variable_name, value)
80
+ evaluation_context.instance_exec(&block)
88
81
  end
89
82
  else
90
- monad.flat_map do |value|
83
+ resolve(monad).flat_map do |value|
84
+ evaluation_context.__assign__(variable_name, value)
91
85
  variable_name_and_monad, *tail = *monads
92
- execute(locals.merge(variable_name => value), *variable_name_and_monad, tail, &block)
86
+ execute(*variable_name_and_monad, tail, &block)
93
87
  end
94
88
  end
95
89
  end
96
90
 
91
+ def resolve(monad_or_proc)
92
+ if monad_or_proc.respond_to?(:call)
93
+ evaluation_context.instance_exec(&monad_or_proc)
94
+ else
95
+ monad_or_proc
96
+ end
97
+ end
98
+
97
99
  # Include this mixin to access convenient factory method for +For+.
98
100
  # @example
99
101
  # include Fear::For::Mixin
100
102
  #
101
- # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
102
- # For(a: Some(2), b: None()) { a * b } #=> None()
103
+ # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
104
+ # For(a: Some(2), b: None()) { a * b } #=> None()
105
+ #
106
+ # For(a: -> { Some(2) }, b: -> { Some(3) }) do
107
+ # a * b
108
+ # end #=> Some(6)
109
+ #
110
+ # For(a: -> { None() }, b: -> { fail }) do
111
+ # a * b
112
+ # end #=> None()
103
113
  #
104
114
  # For(a: Right(2), b: Right(3)) { a * b } #=> Right(6)
105
115
  # For(a: Right(2), b: Left(3)) { a * b } #=> Left(3)
@@ -112,7 +122,7 @@ module Fear
112
122
  # @return [{#map, #flat_map}]
113
123
  #
114
124
  def For(**locals, &block)
115
- For.new(**locals).call(&block)
125
+ For.new(self, **locals).call(&block)
116
126
  end
117
127
  end
118
128
  end
data/lib/fear/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fear
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
@@ -17,6 +17,18 @@ RSpec.describe Fear::For do
17
17
  end
18
18
  end
19
19
 
20
+ context 'access method from outer scope' do
21
+ def two
22
+ 2
23
+ end
24
+
25
+ subject do
26
+ For(a: Some(2)) { a * two }
27
+ end
28
+
29
+ it { is_expected.to eq(Some(4)) }
30
+ end
31
+
20
32
  context 'arrays' do
21
33
  subject do
22
34
  For(a: [1, 2], b: [2, 3], c: [3, 4]) do
@@ -41,7 +53,7 @@ RSpec.describe Fear::For do
41
53
  it { is_expected.to eq(Some(24)) }
42
54
  end
43
55
 
44
- context 'first None' do
56
+ context 'first is None' do
45
57
  let(:first) { None() }
46
58
  let(:second) { Some(3) }
47
59
  let(:third) { Some(4) }
@@ -49,7 +61,7 @@ RSpec.describe Fear::For do
49
61
  it { is_expected.to eq(None()) }
50
62
  end
51
63
 
52
- context 'second None' do
64
+ context 'second is None' do
53
65
  let(:first) { Some(2) }
54
66
  let(:second) { None() }
55
67
  let(:third) { Some(4) }
@@ -57,12 +69,54 @@ RSpec.describe Fear::For do
57
69
  it { is_expected.to eq(None()) }
58
70
  end
59
71
 
60
- context 'last None' do
72
+ context 'last is None' do
61
73
  let(:first) { Some(2) }
62
74
  let(:second) { Some(3) }
63
75
  let(:third) { None() }
64
76
 
65
77
  it { is_expected.to eq(None()) }
66
78
  end
79
+
80
+ context 'all Same in lambdas' do
81
+ let(:first) { -> { Some(2) } }
82
+ let(:second) { -> { Some(3) } }
83
+ let(:third) { -> { Some(4) } }
84
+
85
+ it { is_expected.to eq(Some(24)) }
86
+ end
87
+
88
+ context 'first is None in lambda, second is failure in lambda' do
89
+ let(:first) { -> { None() } }
90
+ let(:second) { -> { fail 'kaboom' } }
91
+ let(:third) { -> {} }
92
+
93
+ it 'returns None without evaluating second and third' do
94
+ is_expected.to eq(None())
95
+ end
96
+ end
97
+
98
+ context 'second is None in lambda, third is failure in lambda' do
99
+ let(:first) { Some(2) }
100
+ let(:second) { -> { None() } }
101
+ let(:third) { -> { fail 'kaboom' } }
102
+
103
+ it 'returns None without evaluating third' do
104
+ is_expected.to eq(None())
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'refer to previous variable from lambda' do
110
+ subject do
111
+ For(a: first, b: second, c: third) do
112
+ b * c
113
+ end
114
+ end
115
+
116
+ let(:first) { Some(Some(2)) }
117
+ let(:second) { -> { a } }
118
+ let(:third) { -> { Some(3) } }
119
+
120
+ it { is_expected.to eq(Some(6)) }
67
121
  end
68
122
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tema Bolshakov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-13 00:00:00.000000000 Z
11
+ date: 2016-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-equalizer
@@ -129,6 +129,7 @@ files:
129
129
  - lib/fear/either.rb
130
130
  - lib/fear/failure.rb
131
131
  - lib/fear/for.rb
132
+ - lib/fear/for/evaluation_context.rb
132
133
  - lib/fear/left.rb
133
134
  - lib/fear/none.rb
134
135
  - lib/fear/option.rb