fear 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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