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 +4 -4
- data/lib/fear/for/evaluation_context.rb +91 -0
- data/lib/fear/for.rb +48 -38
- data/lib/fear/version.rb +1 -1
- data/spec/fear/for_spec.rb +57 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cbecbc38d219215fb5b302a437db94936dd1d41
|
4
|
+
data.tar.gz: ed92b8831f89e7a31f7a91ab8a9e3992ab36dec0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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(
|
68
|
+
execute(*variable_name_and_monad, tail, &block)
|
80
69
|
end
|
81
70
|
|
82
71
|
private
|
83
72
|
|
84
|
-
|
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
|
-
|
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(
|
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 }
|
102
|
-
# For(a: Some(2), b: None()) { a * b }
|
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
data/spec/fear/for_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|