deterministic 0.14.1 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +108 -41
- data/lib/deterministic.rb +1 -0
- data/lib/deterministic/enum.rb +203 -0
- data/lib/deterministic/monad.rb +19 -5
- data/lib/deterministic/option.rb +40 -80
- data/lib/deterministic/protocol.rb +103 -0
- data/lib/deterministic/result.rb +49 -91
- data/lib/deterministic/version.rb +1 -1
- data/spec/examples/amount_spec.rb +67 -0
- data/spec/examples/config_spec.rb +5 -2
- data/spec/examples/controller_spec.rb +1 -1
- data/spec/examples/list.rb +183 -0
- data/spec/examples/list_spec.rb +186 -0
- data/spec/examples/logger_spec.rb +13 -8
- data/spec/examples/validate_address_spec.rb +5 -4
- data/spec/lib/deterministic/class_mixin_spec.rb +3 -4
- data/spec/lib/deterministic/core_ext/result_spec.rb +4 -2
- data/spec/lib/deterministic/currify_spec.rb +88 -0
- data/spec/lib/deterministic/monad_spec.rb +3 -3
- data/spec/lib/deterministic/option_spec.rb +107 -98
- data/spec/lib/deterministic/protocol_spec.rb +43 -0
- data/spec/lib/deterministic/result/failure_spec.rb +1 -7
- data/spec/lib/deterministic/result/{result_map.rb → result_map_spec.rb} +9 -9
- data/spec/lib/deterministic/result/success_spec.rb +1 -2
- data/spec/lib/deterministic/result_spec.rb +44 -15
- data/spec/lib/enum_spec.rb +108 -0
- data/spec/spec_helper.rb +2 -1
- metadata +17 -8
- data/.rspec +0 -2
- data/spec/examples/bookings_spec.rb +0 -72
- data/spec/lib/deterministic/result/match_spec.rb +0 -116
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'deterministic/enum'
|
3
|
+
|
4
|
+
Amount = Deterministic::enum {
|
5
|
+
Due(:amount)
|
6
|
+
Paid(:amount)
|
7
|
+
Info(:amount)
|
8
|
+
}
|
9
|
+
|
10
|
+
class Amount
|
11
|
+
def self.from_f(f)
|
12
|
+
f >= 0 ? Amount::Due.new(f) : Amount::Paid.new(-1 * f)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Deterministic::impl(Amount) {
|
17
|
+
def to_s
|
18
|
+
match {
|
19
|
+
Due(a) { "%0.2f" % [a] }
|
20
|
+
Paid(a) { "-%0.2f" % [a] }
|
21
|
+
Info(a) { "(%0.2f)" % [a] }
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_f
|
26
|
+
match {
|
27
|
+
Info(a) { 0 }
|
28
|
+
Due(a) { a }
|
29
|
+
Paid(a) { -1 * a }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def +(other)
|
34
|
+
raise TypeError "Expected other to be an Amount, got #{other.class}" unless other.is_a? Amount
|
35
|
+
|
36
|
+
Amount.from_f(to_f + other.to_f)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
|
40
|
+
describe Amount do
|
41
|
+
def Due(a); Amount::Due.new(a); end
|
42
|
+
def Paid(a); Amount::Paid.new(a); end
|
43
|
+
def Info(a); Amount::Info.new(a); end
|
44
|
+
|
45
|
+
it "due" do
|
46
|
+
amount = Amount::Due.new(100.2)
|
47
|
+
expect(amount.to_s).to eq "100.20"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "paid" do
|
51
|
+
amount = Amount::Paid.new(100.1)
|
52
|
+
expect(amount.to_s).to eq "-100.10"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "paid" do
|
56
|
+
amount = Amount::Info.new(100.31)
|
57
|
+
expect(amount.to_s).to eq "(100.31)"
|
58
|
+
end
|
59
|
+
|
60
|
+
it "+" do
|
61
|
+
expect(Due(10) + Paid(20)).to eq Paid(10)
|
62
|
+
expect(Due(10) + Paid(10)).to eq Due(0)
|
63
|
+
expect(Due(10) + Due(10)).to eq Due(20)
|
64
|
+
expect(Paid(10) + Paid(10)).to eq Paid(20)
|
65
|
+
expect(Paid(10) + Due(1) + Info(99)).to eq Paid(9)
|
66
|
+
end
|
67
|
+
end
|
@@ -11,8 +11,8 @@ class ElasticSearchConfig
|
|
11
11
|
|
12
12
|
def hosts
|
13
13
|
Option.any?(proc_env["RESFINITY_LOG_CLIENT_ES_HOST"]).match {
|
14
|
-
|
15
|
-
|
14
|
+
Some(s) { { hosts: s.split(/, */) } }
|
15
|
+
None() { default_hosts }
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
@@ -31,6 +31,8 @@ private
|
|
31
31
|
end
|
32
32
|
|
33
33
|
describe ElasticSearchConfig do
|
34
|
+
pending("match exec context must have access to parents block binding context") {
|
35
|
+
|
34
36
|
let(:cfg) { ElasticSearchConfig.new(environment, env) }
|
35
37
|
context "test" do
|
36
38
|
let(:environment) { "test" }
|
@@ -70,4 +72,5 @@ describe ElasticSearchConfig do
|
|
70
72
|
specify { expect(cfg.hosts).to eq({ hosts: ["acc.resfinity.net:9200"] }) }
|
71
73
|
end
|
72
74
|
end
|
75
|
+
}
|
73
76
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'deterministic/enum'
|
2
|
+
|
3
|
+
List = Deterministic::enum {
|
4
|
+
Cons(:head, :tail)
|
5
|
+
Nil()
|
6
|
+
}
|
7
|
+
|
8
|
+
class List
|
9
|
+
def self.[](*ary)
|
10
|
+
ary.reverse.inject(Nil.new) { |xs, x| xs.append(x) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.empty
|
14
|
+
@empty ||= Nil.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Deterministic::impl(List) {
|
19
|
+
class EmptyListError < StandardError; end
|
20
|
+
|
21
|
+
def append(elem)
|
22
|
+
List::Cons.new(elem, self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def null?
|
26
|
+
is_a? Nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def first
|
30
|
+
match {
|
31
|
+
Cons(_, _) { |c| c }
|
32
|
+
Nil() { |n| n }
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def last
|
37
|
+
match {
|
38
|
+
Cons(h, t, where { t.null? }) { |c| return c }
|
39
|
+
Cons(_, t) { t.last }
|
40
|
+
Nil() { |n| n }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def head
|
45
|
+
match {
|
46
|
+
Cons(h, _) { h }
|
47
|
+
Nil() { |n| n }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def tail
|
52
|
+
match {
|
53
|
+
Cons(_, t) { t }
|
54
|
+
Nil() { |n| raise EmptyListError }
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def init
|
59
|
+
match {
|
60
|
+
Cons(h, t, where { |c| t.tail.null? } ) { |c| Cons.new(h, Nil.new) }
|
61
|
+
Cons(h, t) { |c| Cons.new(h, t.init) }
|
62
|
+
Nil() { raise EmptyListError }
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter(&pred)
|
67
|
+
match {
|
68
|
+
Cons(h, t, where { pred.(h) }) { |c| Cons.new(h, t.filter(&pred)) }
|
69
|
+
Cons(_, t) { t.filter(&pred) }
|
70
|
+
Nil() { |n| n }
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# The find function takes a predicate and a list and returns the first element in the list matching the predicate,
|
75
|
+
# or None if there is no such element.
|
76
|
+
def find(&pred)
|
77
|
+
match {
|
78
|
+
Nil() { Deterministic::Option::None.new }
|
79
|
+
Cons(h, t) { if pred.(h) then Deterministic::Option::Some.new(h) else t.find(&pred) end }
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def length
|
84
|
+
match {
|
85
|
+
Cons(h, t) { 1 + t.length }
|
86
|
+
Nil() { 0 }
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def map(&fn)
|
91
|
+
match {
|
92
|
+
Cons(h, t) { Cons.new(fn.(h), t.map(&fn)) }
|
93
|
+
Nil() { |n| n }
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def sum
|
98
|
+
foldl(0, &:+)
|
99
|
+
end
|
100
|
+
|
101
|
+
def foldl(start, &fn)
|
102
|
+
match {
|
103
|
+
Nil() { start }
|
104
|
+
# foldl f z (x:xs) = foldl f (f z x) xs
|
105
|
+
Cons(h, t) { t.foldl(fn.(start, h), &fn) }
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def foldl1(&fn)
|
110
|
+
match {
|
111
|
+
Nil() { raise EmptyListError }
|
112
|
+
Cons(h, t) { t.foldl(h, &fn)}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
def foldr(start, &fn)
|
117
|
+
match {
|
118
|
+
Nil() { start }
|
119
|
+
# foldr f z (x:xs) = f x (foldr f z xs)
|
120
|
+
Cons(h, t) { fn.(h, t.foldr(start, &fn)) }
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def foldr1(&fn)
|
125
|
+
match {
|
126
|
+
Nil() { raise EmptyListError }
|
127
|
+
Cons(h, t, where { t.null? }) { h }
|
128
|
+
# foldr1 f (x:xs) = f x (foldr1 f xs)
|
129
|
+
Cons(h, t) { fn.(h, t.foldr1(&fn)) }
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def take(n)
|
134
|
+
match {
|
135
|
+
Cons(h, t, where { n > 0 }) { Cons.new(h, t.take(n - 1))}
|
136
|
+
Cons(_, _) { Nil.new }
|
137
|
+
Nil() { raise EmptyListError}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def drop(n)
|
142
|
+
match {
|
143
|
+
Cons(h, t, where { n > 0 }) { t.drop(n - 1) }
|
144
|
+
Cons(_, _) { |c| c }
|
145
|
+
Nil() { raise EmptyListError}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_a
|
150
|
+
foldr([]) { |x, ary| ary << x }
|
151
|
+
end
|
152
|
+
|
153
|
+
def any?(&pred)
|
154
|
+
match {
|
155
|
+
Nil() { false }
|
156
|
+
Cons(h, t, where { t.null? }) { pred.(h) }
|
157
|
+
Cons(h, t) { pred.(h) || t.any?(&pred) }
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def all?(&pred)
|
162
|
+
match {
|
163
|
+
Nil() { false }
|
164
|
+
Cons(h, t, where { t.null? }) { pred.(h) }
|
165
|
+
Cons(h, t) { pred.(h) && t.all?(&pred) }
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def reverse
|
170
|
+
match {
|
171
|
+
Nil() { |n| n }
|
172
|
+
Cons(_, t, where { t.null? }) { |c| c }
|
173
|
+
Cons(h, t) { |c| Cons.new(c.last.head, c.init.reverse) }
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_s(joiner = ", ")
|
178
|
+
match {
|
179
|
+
Nil() { "Nil" }
|
180
|
+
Cons(head, tail) { head.to_s + joiner + tail.to_s }
|
181
|
+
}
|
182
|
+
end
|
183
|
+
}
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'list'
|
3
|
+
|
4
|
+
describe List do
|
5
|
+
Nil = List::Nil
|
6
|
+
Cons = List::Cons
|
7
|
+
|
8
|
+
subject(:list) { List[1] }
|
9
|
+
|
10
|
+
specify { expect(list).to eq Cons.new(1, Nil.new) }
|
11
|
+
|
12
|
+
context "match" do
|
13
|
+
it "catches ignores guards with non-matching clauses" do
|
14
|
+
expect(
|
15
|
+
list.match {
|
16
|
+
Nil() { self }
|
17
|
+
Cons(h, t, where { h == 0 }) { h }
|
18
|
+
Cons(h, t) { h }
|
19
|
+
}).to eq 1
|
20
|
+
end
|
21
|
+
|
22
|
+
it "catches matching guards" do
|
23
|
+
expect( # guard catched
|
24
|
+
list.match {
|
25
|
+
Nil() { raise "unreachable" }
|
26
|
+
Cons(h, t, where { h == 1 }) { h + 1 }
|
27
|
+
Cons(h, t) { h }
|
28
|
+
}).to eq 2
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises an error when no match was made" do
|
32
|
+
expect {
|
33
|
+
list.match {
|
34
|
+
Cons(_, _, where { true == false }) { 1 }
|
35
|
+
Nil(where { true == false }) { 0 }
|
36
|
+
}
|
37
|
+
}.to raise_error(Deterministic::Enum::MatchError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an error when the match is not exhaustive" do
|
41
|
+
expect {
|
42
|
+
list.match {
|
43
|
+
Cons(_, _) {}
|
44
|
+
}
|
45
|
+
}.to raise_error(Deterministic::Enum::MatchError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "empty" do
|
50
|
+
expect(List.empty.object_id).to eq List.empty.object_id
|
51
|
+
end
|
52
|
+
|
53
|
+
it "from_a" do
|
54
|
+
expect(List[21, 15, 9].to_s).to eq "21, 15, 9, Nil"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "append" do
|
58
|
+
expect(List[1].append(2)).to eq Cons.new(2, Cons.new(1, Nil.new))
|
59
|
+
end
|
60
|
+
|
61
|
+
context "head" do
|
62
|
+
specify { expect(list.head).to eq 1 }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "tail" do
|
66
|
+
subject(:list) { List[3, 9, 15, 21] }
|
67
|
+
specify { expect(list.tail.to_s).to eq "9, 15, 21, Nil" }
|
68
|
+
end
|
69
|
+
|
70
|
+
context "take" do
|
71
|
+
subject(:list) { List[3, 9, 15, 21] }
|
72
|
+
specify { expect(list.take(2).to_s).to eq "3, 9, Nil" }
|
73
|
+
end
|
74
|
+
|
75
|
+
context "drop" do
|
76
|
+
subject(:list) { List[3, 9, 15, 21] }
|
77
|
+
specify { expect(list.drop(2).to_s).to eq "15, 21, Nil" }
|
78
|
+
end
|
79
|
+
|
80
|
+
context "null" do
|
81
|
+
specify { expect(Nil.new).to be_null }
|
82
|
+
specify { expect(Cons.new(1, Nil.new)).not_to be_null }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "length" do
|
86
|
+
subject(:list) { List[21, 15, 9, 3] }
|
87
|
+
specify { expect(list.length).to eq 4 }
|
88
|
+
specify { expect(Nil.new.length).to eq 0 }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "filter" do
|
92
|
+
subject(:list) { List[3, 9, 15, 21] }
|
93
|
+
specify { expect(list.filter { |n| n < 10 }.to_s).to eq "3, 9, Nil" }
|
94
|
+
specify { expect(Nil.new.filter { |n| n < 10 }).to eq Nil.new }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "map" do
|
98
|
+
subject(:list) { List[1, 2, 3, 4] }
|
99
|
+
|
100
|
+
specify { expect(list.map { |h, t| h + 1 }).to eq Cons.new(2, Cons.new(3, Cons.new(4, Cons.new(5, Nil.new)))) }
|
101
|
+
end
|
102
|
+
|
103
|
+
context "first & last" do
|
104
|
+
subject(:list) { List[9, 15, 21] }
|
105
|
+
specify { expect(list.first.head).to eq 9 }
|
106
|
+
specify { expect(list.last.head).to eq 21 }
|
107
|
+
|
108
|
+
specify { expect(Nil.new.first).to eq Nil.new }
|
109
|
+
specify { expect(Nil.new.last).to eq Nil.new }
|
110
|
+
end
|
111
|
+
|
112
|
+
context "init" do
|
113
|
+
subject(:list) { List[9, 15, 21] }
|
114
|
+
specify { expect(list.init.to_s).to eq "9, 15, Nil" }
|
115
|
+
specify { expect { Nil.new.init}.to raise_error EmptyListError }
|
116
|
+
end
|
117
|
+
|
118
|
+
it "foldl :: [a] -> b -> (b -> a -> b) -> b" do
|
119
|
+
list = List[21, 15, 9]
|
120
|
+
expect(list.foldl(0) { |b, a| a + b }).to eq (((0 + 21) + 15) + 9)
|
121
|
+
expect(list.foldl(0) { |b, a| b - a }).to eq (((0 - 21) - 15) - 9)
|
122
|
+
expect(Nil.new.foldl(0, &:+)).to eq 0
|
123
|
+
end
|
124
|
+
|
125
|
+
it "foldl1 :: [a] -> b -> (b -> a -> b) -> b" do
|
126
|
+
list = List[21, 15, 9]
|
127
|
+
expect(list.foldl1 { |b, a| a + b }).to eq ((21 + 15) + 9)
|
128
|
+
expect(list.foldl1 { |b, a| b - a }).to eq ((21 - 15) - 9)
|
129
|
+
expect { Nil.new.foldl1(&:+) }.to raise_error EmptyListError
|
130
|
+
end
|
131
|
+
|
132
|
+
it "foldr :: [a] -> b -> (b -> a -> b) -> b" do
|
133
|
+
list = List[21, 15, 9]
|
134
|
+
expect(list.foldr(0) { |b, a| a + b }).to eq (21 + (15 + (9 + 0)))
|
135
|
+
expect(list.foldr(0) { |b, a| b - a }).to eq (21 - (15 - (9 - 0)))
|
136
|
+
expect(Nil.new.foldr(0, &:+)).to eq 0
|
137
|
+
end
|
138
|
+
|
139
|
+
it "foldr1 :: [a] -> b -> (b -> a -> b) -> b" do
|
140
|
+
list = List[21, 15, 9, 3]
|
141
|
+
expect(list.foldr1 { |b, a| a + b }).to eq (21 + (15 + (9 + 3)))
|
142
|
+
expect(list.foldr1 { |b, a| b - a }).to eq (21 - (15 - (9 - 3)))
|
143
|
+
expect { Nil.new.foldr1(&:+) }.to raise_error EmptyListError
|
144
|
+
end
|
145
|
+
|
146
|
+
it "find :: [a] -> (a -> Bool) -> Option a" do
|
147
|
+
list = List[21, 15, 9]
|
148
|
+
expect(list.find { |a| a == 15 }).to eq Deterministic::Option::Some.new(15)
|
149
|
+
expect(list.find { |a| a == 1 }).to eq Deterministic::Option::None.new
|
150
|
+
end
|
151
|
+
|
152
|
+
context "reverse" do
|
153
|
+
subject(:list) { List[9, 15, 21] }
|
154
|
+
specify { expect(list.reverse.first.head).to eq 21 }
|
155
|
+
specify { expect(list.to_s).to eq "9, 15, 21, Nil" }
|
156
|
+
specify { expect(list.reverse.to_s).to eq "21, 15, 9, Nil" }
|
157
|
+
end
|
158
|
+
|
159
|
+
context "to_a" do
|
160
|
+
subject(:list) { List[9, 15, 21] }
|
161
|
+
specify { expect(list.to_a).to eq [21, 15, 9] }
|
162
|
+
end
|
163
|
+
|
164
|
+
context "all?" do
|
165
|
+
subject(:list) { List[21, 15, 9] }
|
166
|
+
specify { expect(list.all? { |n| n.is_a?(Fixnum) }).to be_truthy }
|
167
|
+
end
|
168
|
+
|
169
|
+
context "any?" do
|
170
|
+
subject(:list) { List[21, 15, 9] }
|
171
|
+
specify { expect(list.any? { |n| n == 11 }).to be_falsey }
|
172
|
+
specify { expect(list.any? { |n| n == 15 }).to be_truthy }
|
173
|
+
end
|
174
|
+
|
175
|
+
it "inspect" do
|
176
|
+
list = List[9, 15, 21]
|
177
|
+
expect(list.inspect).to eq "Cons(head: 9, tail: Cons(head: 15, tail: Cons(head: 21, tail: Nil)))"
|
178
|
+
expect(Nil.new.inspect).to eq "Nil"
|
179
|
+
end
|
180
|
+
|
181
|
+
it "to_s" do
|
182
|
+
list = List[9, 15, 21]
|
183
|
+
expect(list.to_s).to eq "9, 15, 21, Nil"
|
184
|
+
expect(Nil.new.to_s).to eq "Nil"
|
185
|
+
end
|
186
|
+
end
|