speculation 0.4.0 → 0.4.2

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.
@@ -76,6 +76,13 @@ module Speculation
76
76
  end
77
77
  end
78
78
 
79
+ # @private
80
+ def self.fmap(gen)
81
+ ->(rantly) do
82
+ yield gen.call(rantly)
83
+ end
84
+ end
85
+
79
86
  # @private
80
87
  GEN_BUILTINS = {
81
88
  Integer => ->(r) { r.integer },
@@ -101,6 +108,10 @@ module Speculation
101
108
  gen = Gen.gen_for_pred(Array)
102
109
  Set.new(gen.call(r))
103
110
  end,
111
+ SortedSet => ->(r) do
112
+ gen = Gen.gen_for_pred(Array)
113
+ SortedSet.new(gen.call(r))
114
+ end,
104
115
  Hash => ->(r) do
105
116
  kgen = Gen.gen_for_pred(r.choose(Integer, String, Float, Symbol, Date, Time))
106
117
  vgen = Gen.gen_for_pred(r.choose(Integer, String, Float, Symbol, Date, Time, Set[true, false]))
@@ -13,17 +13,19 @@ module Speculation
13
13
  namespace = name_or_namespace
14
14
  else
15
15
  name = name_or_namespace
16
- namespace = is_a?(Module) ? self.name : self.class.name
16
+ namespace = is_a?(Module) ? self : self.class
17
17
  end
18
18
 
19
19
  NamespacedSymbols.symbol(namespace, name)
20
20
  end
21
21
 
22
22
  def self.symbol(ns, name)
23
+ ns = ns.name if ns.is_a?(Module)
24
+
23
25
  :"#{ns}/#{name}"
24
26
  end
25
27
 
26
- def self.name(sym)
28
+ def self.namespaced_name(sym)
27
29
  sym.to_s.split("/").last
28
30
  end
29
31
 
@@ -0,0 +1,48 @@
1
+ module Speculation
2
+ # Collection of predicate methods used within Speculation.
3
+ # These may appear as the value for the `:pred` key in the return value of
4
+ # `Speculation.explain_data`.
5
+ module Predicates
6
+ def self.hash?(x)
7
+ x.respond_to?(:store) && x.respond_to?(:key?) && x.respond_to?(:[])
8
+ end
9
+
10
+ def self.array?(x)
11
+ x.respond_to?(:at) && x.respond_to?(:[])
12
+ end
13
+
14
+ def self.collection?(xs)
15
+ xs.respond_to?(:each)
16
+ end
17
+
18
+ def self.distinct?(xs)
19
+ seen = Set[]
20
+
21
+ xs.each do |x|
22
+ if seen.include?(x)
23
+ return false
24
+ else
25
+ seen << x
26
+ end
27
+ end
28
+
29
+ true
30
+ end
31
+
32
+ def self.count_eq?(count, coll)
33
+ coll.count == count
34
+ end
35
+
36
+ def self.count_between?(min_count, max_count, coll)
37
+ coll.count.between?(min_count, max_count)
38
+ end
39
+
40
+ def self.key?(hash, key)
41
+ hash.key?(key)
42
+ end
43
+
44
+ def self.empty?(coll)
45
+ coll.empty?
46
+ end
47
+ end
48
+ end
@@ -10,6 +10,10 @@ module Speculation
10
10
  raise NotImplementedError
11
11
  end
12
12
 
13
+ def unform(_x)
14
+ raise NotImplementedError
15
+ end
16
+
13
17
  def explain(_path, _via, _inn, _value)
14
18
  raise NotImplementedError
15
19
  end
@@ -22,12 +22,16 @@ module Speculation
22
22
  @specs.value!.each do |spec|
23
23
  value = spec.conform(value)
24
24
 
25
- return S::INVALID if S.invalid?(value)
25
+ return :"Speculation/invalid" if S.invalid?(value)
26
26
  end
27
27
 
28
28
  value
29
29
  end
30
30
 
31
+ def unform(value)
32
+ @preds.reverse.reduce(value) { |val, pred| S.unform(pred, val) }
33
+ end
34
+
31
35
  def explain(path, via, inn, value)
32
36
  S.explain_pred_list(@preds, path, via, inn, value)
33
37
  end
@@ -38,7 +42,7 @@ module Speculation
38
42
 
39
43
  def gen(overrides, path, rmap)
40
44
  if @gen
41
- @gen
45
+ @gen.call
42
46
  else
43
47
  S.gensub(@preds.first, overrides, path, rmap)
44
48
  end
@@ -28,21 +28,27 @@ module Speculation
28
28
  end)
29
29
  end
30
30
 
31
+ if options[:distinct]
32
+ collection_predicates.push(->(coll) do
33
+ coll.empty? || Predicates.distinct?(coll)
34
+ end)
35
+ end
36
+
31
37
  @collection_predicate = ->(coll) { collection_predicates.all? { |f| f.respond_to?(:call) ? f.call(coll) : f === coll } }
32
38
  @delayed_spec = Concurrent::Delay.new { S.send(:specize, predicate) }
33
- @kfn = options.fetch(ns(S, :kfn), ->(i, _v) { i })
39
+ @kfn = options.fetch(:kfn, ->(i, _v) { i })
34
40
  @conform_keys, @conform_all, @kind, @gen_into, @gen_max, @distinct, @count, @min_count, @max_count =
35
- options.values_at(:conform_keys, ns(S, :conform_all), :kind, :into, :gen_max, :distinct, :count, :min_count, :max_count)
41
+ options.values_at(:conform_keys, :conform_all, :kind, :into, :gen_max, :distinct, :count, :min_count, :max_count)
36
42
  @gen_max ||= 20
37
43
  @conform_into = @gen_into
38
44
 
39
45
  # returns a tuple of [init add complete] fns
40
46
  @cfns = ->(x) do
41
- if Utils.array?(x) && (!@conform_into || Utils.array?(@conform_into))
42
- [Utils.method(:itself),
47
+ if Predicates.array?(x) && (!@conform_into || Predicates.array?(@conform_into))
48
+ [->(init) { init.dup },
43
49
  ->(ret, i, v, cv) { v.equal?(cv) ? ret : ret.tap { |r| r[i] = cv } },
44
50
  Utils.method(:itself)]
45
- elsif Utils.hash?(x) && ((@kind && !@conform_into) || Utils.hash?(@conform_into))
51
+ elsif Predicates.hash?(x) && ((@kind && !@conform_into) || Predicates.hash?(@conform_into))
46
52
  [@conform_keys ? Utils.method(:empty) : Utils.method(:itself),
47
53
  ->(ret, _i, v, cv) {
48
54
  if v.equal?(cv) && !@conform_keys
@@ -61,7 +67,7 @@ module Speculation
61
67
  end
62
68
 
63
69
  def conform(value)
64
- return S::INVALID unless @collection_predicate.call(value)
70
+ return :"Speculation/invalid" unless @collection_predicate.call(value)
65
71
 
66
72
  spec = @delayed_spec.value!
67
73
 
@@ -74,7 +80,7 @@ module Speculation
74
80
  conformed_value = spec.conform(val)
75
81
 
76
82
  if S.invalid?(conformed_value)
77
- return S::INVALID
83
+ return :"Speculation/invalid"
78
84
  else
79
85
  return_value = add.call(return_value, index, val, conformed_value)
80
86
  end
@@ -87,13 +93,30 @@ module Speculation
87
93
 
88
94
  value.each_with_index do |item, index|
89
95
  return value if index == limit
90
- return S::INVALID unless S.valid?(spec, item)
96
+ return :"Speculation/invalid" unless S.valid?(spec, item)
91
97
  end
92
98
 
93
99
  value
94
100
  end
95
101
  end
96
102
 
103
+ def unform(value)
104
+ if @conform_all
105
+ spec = @delayed_spec.value!
106
+ init, add, complete = @cfns.call(value)
107
+
108
+ ret = value.
109
+ each_with_index.
110
+ reduce(init.call(value)) { |memo, (val, index)|
111
+ add.call(memo, index, val, spec.unform(val))
112
+ }
113
+
114
+ complete.call(ret)
115
+ else
116
+ value
117
+ end
118
+ end
119
+
97
120
  def explain(path, via, inn, value)
98
121
  probs = collection_problems(value, @kind, @distinct, @count, @min_count, @max_count, path, via, inn)
99
122
  return probs if probs
@@ -117,7 +140,7 @@ module Speculation
117
140
  end
118
141
 
119
142
  def gen(overrides, path, rmap)
120
- return @gen if @gen
143
+ return @gen.call if @gen
121
144
 
122
145
  pgen = S.gensub(@predicate, overrides, path, rmap)
123
146
 
@@ -132,13 +155,13 @@ module Speculation
132
155
 
133
156
  val = if @distinct
134
157
  if @count
135
- rantly.array(@count, &pgen).tap { |arr| rantly.guard(Utils.distinct?(arr)) }
158
+ rantly.array(@count, &pgen).tap { |arr| rantly.guard(Predicates.distinct?(arr)) }
136
159
  else
137
160
  min = @min_count || 0
138
161
  max = @max_count || [@gen_max, 2 * min].max
139
162
  count = rantly.range(min, max)
140
163
 
141
- rantly.array(count, &pgen).tap { |arr| rantly.guard(Utils.distinct?(arr)) }
164
+ rantly.array(count, &pgen).tap { |arr| rantly.guard(Predicates.distinct?(arr)) }
142
165
  end
143
166
  elsif @count
144
167
  rantly.array(@count, &pgen)
@@ -160,26 +183,26 @@ module Speculation
160
183
  private
161
184
 
162
185
  def collection_problems(x, kfn, distinct, count, min_count, max_count, path, via, inn)
163
- pred = kfn || Utils.method(:collection?)
186
+ pred = kfn || Predicates.method(:collection?)
164
187
 
165
188
  unless S.pvalid?(pred, x)
166
189
  return S.explain1(pred, path, via, inn, x)
167
190
  end
168
191
 
169
- if count && !Utils.count_eq?(x, count)
170
- return [{ :path => path, :pred => [Utils.method(:count_eq?), [x, count]], :val => x, :via => via, :in => inn }]
192
+ if count && !Predicates.count_eq?(count, x)
193
+ return [{ :path => path, :pred => [Predicates.method(:count_eq?), [count, x]], :val => x, :via => via, :in => inn }]
171
194
  end
172
195
 
173
196
  if min_count || max_count
174
197
  min_count ||= 0
175
198
  max_count ||= Float::INFINITY
176
- unless Utils.count_between?(x, min_count, max_count)
177
- return [{ :path => path, :pred => [Utils.method(:count_between?), [x, min_count, max_count]], :val => x, :via => via, :in => inn }]
199
+ unless Predicates.count_between?(min_count, max_count, x)
200
+ return [{ :path => path, :pred => [Predicates.method(:count_between?), [min_count, max_count, x]], :val => x, :via => via, :in => inn }]
178
201
  end
179
202
  end
180
203
 
181
- if distinct && !x.empty? && !Utils.distinct?(x)
182
- [{ :path => path, :pred => [Utils.method(:distinct?), [x]], :val => x, :via => via, :in => inn }]
204
+ if distinct && !x.empty? && !Predicates.distinct?(x)
205
+ [{ :path => path, :pred => [Predicates.method(:distinct?), [x]], :val => x, :via => via, :in => inn }]
183
206
  end
184
207
  end
185
208
  end
@@ -14,7 +14,7 @@ module Speculation
14
14
 
15
15
  def initialize(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
16
16
  @args = args
17
- @ret = ret
17
+ @ret = ret || :"Speculation/any"
18
18
  @fn = fn
19
19
  @block = block
20
20
  @gen = gen
@@ -23,17 +23,21 @@ module Speculation
23
23
  def conform(f)
24
24
  raise "Can't conform fspec without args spec: #{inspect}" unless @args
25
25
 
26
- return S::INVALID unless f.is_a?(Proc) || f.is_a?(Method)
26
+ return :"Speculation/invalid" unless f.is_a?(Proc) || f.is_a?(Method)
27
27
 
28
28
  specs = { :args => @args, :ret => @ret, :fn => @fn, :block => @block }
29
29
 
30
30
  if f.equal?(FSpec.validate_fn(f, specs, S.fspec_iterations))
31
31
  f
32
32
  else
33
- S::INVALID
33
+ :"Speculation/invalid"
34
34
  end
35
35
  end
36
36
 
37
+ def unform(f)
38
+ f
39
+ end
40
+
37
41
  def explain(path, via, inn, f)
38
42
  unless f.respond_to?(:call)
39
43
  return [{ :path => path, :pred => [f.method(:respond_to?), [:call]], :val => f, :via => via, :in => inn }]
@@ -66,7 +70,7 @@ module Speculation
66
70
  end
67
71
 
68
72
  def gen(overrides, _path, _rmap)
69
- return @gen if @gen
73
+ return @gen.call if @gen
70
74
 
71
75
  ->(_rantly) do
72
76
  ->(*args, &block) do
@@ -31,7 +31,7 @@ module Speculation
31
31
  req_specs = req_keys + req_un_specs
32
32
  req_keys += req_un_specs.map(&method(:unqualify_key))
33
33
 
34
- pred_exprs = [Utils.method(:hash?)]
34
+ pred_exprs = [Predicates.method(:hash?)]
35
35
  pred_exprs.push(->(v) { parse_req(req, v, Utils.method(:itself)).empty? }) if req.any?
36
36
  pred_exprs.push(->(v) { parse_req(req_un, v, method(:unqualify_key)).empty? }) if req_un.any?
37
37
 
@@ -44,43 +44,61 @@ module Speculation
44
44
  end
45
45
 
46
46
  def conform(value)
47
- return S::INVALID unless @keys_pred.call(value)
47
+ return :"Speculation/invalid" unless @keys_pred.call(value)
48
48
 
49
49
  reg = S.registry
50
- ret = value
51
50
 
52
- value.each do |key, v|
53
- spec_name = @key_to_spec_map.fetch(key, key)
51
+ value.reduce(value) do |ret, (key, v)|
52
+ spec_name = key_to_spec_name(key)
54
53
  spec = reg[spec_name]
55
54
 
56
- next unless spec
55
+ if spec
56
+ conformed_value = S.conform(spec, v)
57
57
 
58
- conformed_value = S.conform(spec, v)
59
-
60
- if S.invalid?(conformed_value)
61
- return S::INVALID
62
- else
63
- unless conformed_value.equal?(v)
64
- ret = ret.merge(key => conformed_value)
58
+ if S.invalid?(conformed_value)
59
+ break :"Speculation/invalid"
60
+ elsif conformed_value.equal?(v)
61
+ ret
62
+ else
63
+ ret.merge(key => conformed_value)
65
64
  end
65
+ else
66
+ ret
66
67
  end
67
68
  end
69
+ end
68
70
 
69
- ret
71
+ def unform(value)
72
+ reg = S.registry
73
+
74
+ value.reduce(value) do |ret, (key, conformed_value)|
75
+ if reg.key?(key_to_spec_name(key))
76
+ unformed_value = S.unform(key_to_spec_name(key), conformed_value)
77
+
78
+ if conformed_value.equal?(unformed_value)
79
+ ret
80
+ else
81
+ ret.merge(key => unformed_value)
82
+ end
83
+ else
84
+ ret
85
+ end
86
+ end
70
87
  end
71
88
 
72
89
  def explain(path, via, inn, value)
73
- unless Utils.hash?(value)
74
- return [{ :path => path, :pred => [Utils.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
90
+ unless Predicates.hash?(value)
91
+ return [{ :path => path, :pred => [Predicates.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
75
92
  end
76
93
 
94
+ reg = S.registry
77
95
  problems = []
78
96
 
79
97
  if @req.any?
80
98
  failures = parse_req(@req, value, Utils.method(:itself))
81
99
 
82
100
  failures.each do |failure_sexp|
83
- pred = [Utils.method(:key?), [failure_sexp]]
101
+ pred = [Predicates.method(:key?), [failure_sexp]]
84
102
  problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
85
103
  end
86
104
  end
@@ -89,17 +107,16 @@ module Speculation
89
107
  failures = parse_req(@req_un, value, method(:unqualify_key))
90
108
 
91
109
  failures.each do |failure_sexp|
92
- pred = [Utils.method(:key?), [failure_sexp]]
110
+ pred = [Predicates.method(:key?), [failure_sexp]]
93
111
  problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
94
112
  end
95
113
  end
96
114
 
97
115
  problems += value.flat_map { |(k, v)|
98
- next unless S.registry.key?(@key_to_spec_map[k])
116
+ next unless reg.key?(key_to_spec_name(k))
117
+ next if S.pvalid?(key_to_spec_name(k), v)
99
118
 
100
- unless S.pvalid?(@key_to_spec_map.fetch(k), v)
101
- S.explain1(@key_to_spec_map.fetch(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
102
- end
119
+ S.explain1(key_to_spec_name(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
103
120
  }
104
121
 
105
122
  problems.compact
@@ -110,7 +127,7 @@ module Speculation
110
127
  end
111
128
 
112
129
  def gen(overrides, path, rmap)
113
- return @gen if @gen
130
+ return @gen.call if @gen
114
131
 
115
132
  rmap = S.inck(rmap, @id)
116
133
 
@@ -149,7 +166,7 @@ module Speculation
149
166
  end
150
167
 
151
168
  def unqualify_key(x)
152
- NamespacedSymbols.name(x).to_sym
169
+ NamespacedSymbols.namespaced_name(x).to_sym
153
170
  end
154
171
 
155
172
  def parse_req(ks, v, f)
@@ -158,20 +175,20 @@ module Speculation
158
175
  ret = if key.is_a?(Array)
159
176
  op, *kks = key
160
177
  case op
161
- when ns(S, :or)
178
+ when :"Speculation/or"
162
179
  if kks.any? { |k| parse_req([k], v, f).empty? }
163
180
  []
164
181
  else
165
182
  transform_keys([key], f)
166
183
  end
167
- when ns(S, :and)
184
+ when :"Speculation/and"
168
185
  if kks.all? { |k| parse_req([k], v, f).empty? }
169
186
  []
170
187
  else
171
188
  transform_keys([key], f)
172
189
  end
173
190
  else
174
- raise "Expected or, and, got #{op}"
191
+ raise "Expected Speculation/or, Speculation/and, got #{op}"
175
192
  end
176
193
  elsif v.key?(f.call(key))
177
194
  []
@@ -190,10 +207,14 @@ module Speculation
190
207
  keys.map { |key|
191
208
  case key
192
209
  when Array then transform_keys(key, f)
193
- when ns(S, :and), ns(S, :or) then key
210
+ when :"Speculation/and", :"Speculation/or" then key
194
211
  else f.call(key)
195
212
  end
196
213
  }
197
214
  end
215
+
216
+ def key_to_spec_name(k)
217
+ @key_to_spec_map.fetch(k, k)
218
+ end
198
219
  end
199
220
  end