namo 0.16.0 → 0.17.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/CHANGELOG +49 -0
- data/README.md +30 -0
- data/Rakefile +1 -1
- data/lib/Namo/Row.rb +30 -6
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +14 -1
- data/test/Namo/Row_test.rb +105 -0
- data/test/namo_test.rb +123 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f84d86032e9397e8db4a4d7a4150474c30215cf9339ec2158a38802aad1a4d49
|
|
4
|
+
data.tar.gz: 830b874e04b36acd7ab848351419ed5ceb848e0108b95602184b7e6131812e01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b409a5004f78619ea60186404e6f156fadbc660755cc9fc3c64cfe8a028a3ffdd7bb35dd252510ece1814674f0b02af7b347b1ea42c1280390e7d7a982f0de0
|
|
7
|
+
data.tar.gz: 15c2c28dafa605ee5a93e5ebb3bee4e5a1f8301dca7cfb11675e1f197db62052975a0f9b74a9096638062b4d40cd85b5752ce5e52c3b0c5f39463fed421aec6c
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260613
|
|
5
|
+
0.17.0: + parameterised formulae — formulae with required parameters beyond (row, namo) receive arguments at access time through Row#[].
|
|
6
|
+
|
|
7
|
+
1. ~ lib/Namo/Row.rb: Row#[] gains a trailing splat and forwards call-site arguments. Dispatch
|
|
8
|
+
generalises from exact arity 2 to required-parameter count via the new private
|
|
9
|
+
collection_scoped? and required_parameter_count — one required parameter or none stays
|
|
10
|
+
row-scoped, two or more call formula.call(self, @namo, *arguments). Settles the 0.15.0
|
|
11
|
+
negative-arity deferral: |row, *rest| and ->(row, namo = nil){} stay row-scoped;
|
|
12
|
+
|row, namo, *fields| is collection-scoped with optional arguments.
|
|
13
|
+
2. ~ lib/Namo/Row.rb: + argument-count enforcement — the new private expected_argument_counts
|
|
14
|
+
and raise_unless_expected_arguments raise ArgumentError ("wrong number of arguments for
|
|
15
|
+
:sma (given 0, expected 2)") on the wrong count for any dimension: too few or too many for
|
|
16
|
+
a fixed-arity formula, fewer than required for a splatted one, any at all for a data
|
|
17
|
+
dimension, a row-scoped formula, or a two-arity formula. Checked before the Namo-context
|
|
18
|
+
guard, whose message generalises from "two-arity formula" to "collection-scoped formula".
|
|
19
|
+
3. ~ lib/namo.rb: the no-arg values (and so coordinates and to_h) materialise via the new
|
|
20
|
+
private materialisable_dimensions, omitting formulae that require arguments (private
|
|
21
|
+
requires_arguments? and required_parameter_count); explicit asks — values(:dim),
|
|
22
|
+
coordinates(:dim), naming the dimension in a projection, selecting on it — raise through
|
|
23
|
+
Row#[]. dimensions and derived_dimensions still list the name; the empty-Namo case returns
|
|
24
|
+
[] without invoking the formula.
|
|
25
|
+
4. ~ test/Namo/Row_test.rb: + "#[] parameterised formulae" describe — arity 3 and 4 receive
|
|
26
|
+
(row, namo, args...), trailing-splat forwarding (arity -3 and -4), one-required-parameter
|
|
27
|
+
procs stay row-scoped, a row-scoped formula calling a parameterised one with arguments,
|
|
28
|
+
missing-Namo-context raise; + "#[] argument-count enforcement" describe — the message
|
|
29
|
+
matrix for too few, too many, splat minimum, and arguments handed to data dimensions,
|
|
30
|
+
row-scoped, two-arity, and missing dimensions.
|
|
31
|
+
5. ~ test/namo_test.rb: + "#[]= parameterised formulae" describe — resolution with arguments
|
|
32
|
+
through yielded Rows, one definition serving different fields and periods, Enumerable
|
|
33
|
+
predicates, a one-arity formula referencing a parameterised one, dimension listing, bulk
|
|
34
|
+
views omitting, explicit values/coordinates/projection/selection raises, carry through
|
|
35
|
+
contraction, selection (windowing over the filtered rows), and set operators, the
|
|
36
|
+
one-arity wrapper materialisation idiom, namo-plus-splat formulae materialising in the
|
|
37
|
+
bulk views, and the empty-Namo case.
|
|
38
|
+
6. ~ README.md: + Parameterised formulae subsection under Formulae — access-time arguments,
|
|
39
|
+
the required-parameter scope rule, argument-count enforcement, materialisation behaviour,
|
|
40
|
+
and the wrapper idiom.
|
|
41
|
+
7. ~ ROADMAP.md: Promote 0.17.0 to shipped with the dispatch-rule, enforcement, and
|
|
42
|
+
materialisation rationale; Current state -> 0.17.0; Summary folds in parameterised
|
|
43
|
+
formulae; next phase -> Namo::Collection (0.18.0). Date bumped.
|
|
44
|
+
8. ~ COMPARISON.md: Parameterised formulae -> shipped (0.17.0), the entry noting access-time
|
|
45
|
+
arguments and argument-count enforcement. Date bumped.
|
|
46
|
+
9. ~ EXAMPLES.md: The finance section's composition blocks (all three Namo stages) corrected
|
|
47
|
+
from max_by, which returns a Row, to sort_by + last(1), which returns the Namo the 0.14.0
|
|
48
|
+
block contract requires — the 1.x stage, parameterised sma included, now runs end to end
|
|
49
|
+
against this release. The finance highlight notes the parameterised formula as shipped
|
|
50
|
+
(0.17.0). Date bumped.
|
|
51
|
+
10. ~ Namo::VERSION: /0.16.0/0.17.0/
|
|
52
|
+
|
|
4
53
|
20260612
|
|
5
54
|
0.16.0: ~ data/formula exclusivity — projection drops the formulae it materialises; * and ** raise on a data/formula name collision.
|
|
6
55
|
|
data/README.md
CHANGED
|
@@ -686,6 +686,36 @@ One-arity formulae are unchanged, and the two forms mix freely — a one-arity f
|
|
|
686
686
|
|
|
687
687
|
Resolving a two-arity formula needs a Namo to window over. A `Row` constructed directly, without one, raises an `ArgumentError` naming the formula rather than letting the missing context surface as an unrelated error.
|
|
688
688
|
|
|
689
|
+
#### Parameterised formulae
|
|
690
|
+
|
|
691
|
+
A formula can declare parameters beyond `(row, namo)`. The arguments arrive at access time, through `Row#[]`, so one definition serves every column and every setting:
|
|
692
|
+
|
|
693
|
+
```ruby
|
|
694
|
+
prices[:sma] = proc do |row, namo, field, period|
|
|
695
|
+
window = namo[symbol: row[:symbol], date: ->(d){d <= row[:date]}].last(period)
|
|
696
|
+
window.sum{|r| r[field]} / window.count.to_f
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
prices.last[:sma, :close, 20] # 20-period moving average of close
|
|
700
|
+
prices.last[:sma, :volume, 50] # 50-period moving average of volume
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
The number of *required* parameters decides a formula's calling convention. One means row-scoped, two or more means collection-scoped, and everything past the second receives the arguments given at the call site. A trailing splat or optional after `(row, namo)` makes the arguments optional — `proc{|row, namo, *fields|}` accepts any number, including none. A proc whose second parameter is optional (`->(row, namo = nil){...}`) requires only one, so it stays row-scoped.
|
|
704
|
+
|
|
705
|
+
Argument counts are enforced. Asking with the wrong number — too few for the formula's parameters, too many for a fixed-arity proc, or any at all for a data dimension or an unparameterised formula — raises an `ArgumentError` stating the counts, rather than letting `nil` flow into the formula body:
|
|
706
|
+
|
|
707
|
+
```ruby
|
|
708
|
+
prices.last[:sma] # ArgumentError: wrong number of arguments for :sma (given 0, expected 2)
|
|
709
|
+
prices.last[:close, 20] # ArgumentError: wrong number of arguments for :close (given 1, expected 0)
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
A formula that requires arguments can't be materialised without them. `values(:sma)`, `coordinates(:sma)`, naming `:sma` in a projection, and selecting on it all raise the same `ArgumentError`; the no-argument `values`, `coordinates`, and `to_h` omit the dimension, returning everything that can be materialised. `dimensions` and `derived_dimensions` still list it — it is queryable, with arguments. To materialise particular values, bind the arguments in a one-arity wrapper and ask for that:
|
|
713
|
+
|
|
714
|
+
```ruby
|
|
715
|
+
prices[:sma_close_20] = proc{|row| row[:sma, :close, 20]}
|
|
716
|
+
prices[:date, :sma_close_20] # materialises per the usual projection rule
|
|
717
|
+
```
|
|
718
|
+
|
|
689
719
|
### Polymorphic `[]=`
|
|
690
720
|
|
|
691
721
|
`[]=` dispatches on the type of the value assigned. A proc registers a formula, as above. Anything else broadcasts the value to every row:
|
data/Rakefile
CHANGED
|
@@ -21,7 +21,7 @@ namespace :docs do
|
|
|
21
21
|
task :md2pdf => :md4print do
|
|
22
22
|
Dir.glob('docs/*.print.md').each do |f|
|
|
23
23
|
pdf = f.sub(/\.md$/, '.pdf')
|
|
24
|
-
sh "pandoc #{f} --pdf-engine=xelatex -V geometry:margin=1in -V mainfont=Charter -V monofont=Menlo -o #{pdf}"
|
|
24
|
+
sh "pandoc #{f} --pdf-engine=xelatex --include-in-header script/print.preamble.tex -V geometry:margin=1in -V mainfont=Charter -V monofont=Menlo -o #{pdf}"
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
data/lib/Namo/Row.rb
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
class Namo
|
|
5
5
|
class Row
|
|
6
|
-
def [](name)
|
|
6
|
+
def [](name, *arguments)
|
|
7
|
+
raise_unless_expected_arguments(name, arguments)
|
|
7
8
|
if @formulae.key?(name)
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
formula = @formulae[name]
|
|
10
|
+
if collection_scoped?(formula)
|
|
10
11
|
raise_unless_namo_context(name)
|
|
11
|
-
|
|
12
|
+
formula.call(self, @namo, *arguments)
|
|
12
13
|
else
|
|
13
|
-
|
|
14
|
+
formula.call(self)
|
|
14
15
|
end
|
|
15
16
|
else
|
|
16
17
|
@row[name]
|
|
@@ -56,9 +57,32 @@ class Namo
|
|
|
56
57
|
@namo = namo
|
|
57
58
|
end
|
|
58
59
|
|
|
60
|
+
def collection_scoped?(formula)
|
|
61
|
+
required_parameter_count(formula) >= 2
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def required_parameter_count(formula)
|
|
65
|
+
formula.arity >= 0 ? formula.arity : -formula.arity - 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def expected_argument_counts(name)
|
|
69
|
+
formula = @formulae[name]
|
|
70
|
+
return [0, 0] unless formula && collection_scoped?(formula)
|
|
71
|
+
minimum = required_parameter_count(formula) - 2
|
|
72
|
+
maximum = formula.arity >= 0 ? minimum : nil
|
|
73
|
+
[minimum, maximum]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def raise_unless_expected_arguments(name, arguments)
|
|
77
|
+
minimum, maximum = expected_argument_counts(name)
|
|
78
|
+
return if arguments.length >= minimum && (maximum.nil? || arguments.length <= maximum)
|
|
79
|
+
expected = maximum.nil? ? "#{minimum}+" : minimum.to_s
|
|
80
|
+
raise ArgumentError, "wrong number of arguments for #{name.inspect} (given #{arguments.length}, expected #{expected})"
|
|
81
|
+
end
|
|
82
|
+
|
|
59
83
|
def raise_unless_namo_context(name)
|
|
60
84
|
unless @namo
|
|
61
|
-
raise ArgumentError, "
|
|
85
|
+
raise ArgumentError, "collection-scoped formula #{name.inspect} requires a Namo context, but this Row has none"
|
|
62
86
|
end
|
|
63
87
|
end
|
|
64
88
|
end
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -28,7 +28,7 @@ class Namo
|
|
|
28
28
|
|
|
29
29
|
def values(*dims)
|
|
30
30
|
if dims.empty?
|
|
31
|
-
|
|
31
|
+
materialisable_dimensions.each_with_object({}){|dim, hash| hash[dim] = values_for(dim)}
|
|
32
32
|
elsif dims.length == 1
|
|
33
33
|
values_for(dims.first)
|
|
34
34
|
else
|
|
@@ -246,6 +246,19 @@ class Namo
|
|
|
246
246
|
end
|
|
247
247
|
end
|
|
248
248
|
|
|
249
|
+
def materialisable_dimensions
|
|
250
|
+
dimensions.reject{|dim| requires_arguments?(dim)}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def requires_arguments?(name)
|
|
254
|
+
formula = @formulae[name]
|
|
255
|
+
!!formula && required_parameter_count(formula) > 2
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def required_parameter_count(formula)
|
|
259
|
+
formula.arity >= 0 ? formula.arity : -formula.arity - 1
|
|
260
|
+
end
|
|
261
|
+
|
|
249
262
|
def raise_unless_namo(other)
|
|
250
263
|
unless other.is_a?(Namo)
|
|
251
264
|
raise TypeError, "can't compare Namo with #{other.class}"
|
data/test/Namo/Row_test.rb
CHANGED
|
@@ -94,6 +94,111 @@ describe Namo::Row do
|
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
describe "#[] parameterised formulae" do
|
|
98
|
+
let(:namo) do
|
|
99
|
+
Namo.new([row_data])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
let(:contextual_row) do
|
|
103
|
+
Namo::Row.new(row_data, formulae, namo)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "calls an arity-3 formula with the Row, the yielding Namo, and one argument" do
|
|
107
|
+
seen = nil
|
|
108
|
+
formulae[:scaled] = ->(r, n, factor){seen = [r, n, factor]; r[:price] * factor}
|
|
109
|
+
_(contextual_row[:scaled, 3]).must_equal 30.0
|
|
110
|
+
_(seen[0]).must_be_same_as contextual_row
|
|
111
|
+
_(seen[1].equal?(namo)).must_equal true
|
|
112
|
+
_(seen[2]).must_equal 3
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "calls an arity-4 formula with two arguments" do
|
|
116
|
+
formulae[:metric] = ->(r, n, field, factor){r[field] * factor}
|
|
117
|
+
_(contextual_row[:metric, :quantity, 2]).must_equal 200
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "forwards a trailing splat's arguments past a required one (arity -4)" do
|
|
121
|
+
formulae[:dim] = proc{|r, n, field, *rest| [field, rest]}
|
|
122
|
+
_(contextual_row[:dim, :price]).must_equal [:price, []]
|
|
123
|
+
_(contextual_row[:dim, :price, 1, 2]).must_equal [:price, [1, 2]]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "treats a splat directly after namo as collection-scoped taking any number of arguments (arity -3)" do
|
|
127
|
+
formulae[:dim] = proc{|r, n, *rest| rest}
|
|
128
|
+
_(contextual_row[:dim]).must_equal []
|
|
129
|
+
_(contextual_row[:dim, 1, 2, 3]).must_equal [1, 2, 3]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "keeps a one-required-parameter proc row-scoped regardless of trailing optionals" do
|
|
133
|
+
seen = :unset
|
|
134
|
+
formulae[:dim] = ->(r, n = :fallback){seen = n; 1}
|
|
135
|
+
contextual_row[:dim]
|
|
136
|
+
_(seen).must_equal :fallback
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "lets a row-scoped formula call a parameterised formula with arguments" do
|
|
140
|
+
formulae[:metric] = ->(r, n, field, factor){r[field] * factor}
|
|
141
|
+
formulae[:double_quantity] = ->(r){r[:metric, :quantity, 2]}
|
|
142
|
+
_(contextual_row[:double_quantity]).must_equal 200
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "raises ArgumentError naming the formula when a parameterised formula has no Namo context" do
|
|
146
|
+
formulae[:metric] = ->(r, n, field){r[field]}
|
|
147
|
+
error = _(proc{row[:metric, :price]}).must_raise ArgumentError
|
|
148
|
+
_(error.message).must_match(/metric/)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "#[] argument-count enforcement" do
|
|
153
|
+
let(:namo) do
|
|
154
|
+
Namo.new([row_data])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
let(:contextual_row) do
|
|
158
|
+
Namo::Row.new(row_data, formulae, namo)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "raises when a parameterised formula is given too few arguments" do
|
|
162
|
+
formulae[:metric] = ->(r, n, field, period){r[field] * period}
|
|
163
|
+
error = _(proc{contextual_row[:metric, :price]}).must_raise ArgumentError
|
|
164
|
+
_(error.message).must_equal "wrong number of arguments for :metric (given 1, expected 2)"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "raises when a fixed-arity parameterised formula is given too many arguments" do
|
|
168
|
+
formulae[:metric] = ->(r, n, field){r[field]}
|
|
169
|
+
error = _(proc{contextual_row[:metric, :price, 20]}).must_raise ArgumentError
|
|
170
|
+
_(error.message).must_equal "wrong number of arguments for :metric (given 2, expected 1)"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "raises when a splatted parameterised formula is given fewer than its required arguments" do
|
|
174
|
+
formulae[:metric] = proc{|r, n, field, *rest| r[field]}
|
|
175
|
+
error = _(proc{contextual_row[:metric]}).must_raise ArgumentError
|
|
176
|
+
_(error.message).must_equal "wrong number of arguments for :metric (given 0, expected 1+)"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "raises when arguments are given for a data dimension" do
|
|
180
|
+
error = _(proc{row[:price, 20]}).must_raise ArgumentError
|
|
181
|
+
_(error.message).must_equal "wrong number of arguments for :price (given 1, expected 0)"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "raises when arguments are given for a row-scoped formula" do
|
|
185
|
+
formulae[:revenue] = proc{|r| r[:price] * r[:quantity]}
|
|
186
|
+
error = _(proc{row[:revenue, 20]}).must_raise ArgumentError
|
|
187
|
+
_(error.message).must_equal "wrong number of arguments for :revenue (given 1, expected 0)"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "raises when arguments are given for a two-arity formula" do
|
|
191
|
+
formulae[:row_count] = ->(r, n){n.count}
|
|
192
|
+
error = _(proc{contextual_row[:row_count, 1]}).must_raise ArgumentError
|
|
193
|
+
_(error.message).must_equal "wrong number of arguments for :row_count (given 1, expected 0)"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "raises when arguments are given for a missing dimension" do
|
|
197
|
+
error = _(proc{row[:missing, 1]}).must_raise ArgumentError
|
|
198
|
+
_(error.message).must_equal "wrong number of arguments for :missing (given 1, expected 0)"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
97
202
|
describe "#match?" do
|
|
98
203
|
it "matches a single value" do
|
|
99
204
|
_(row.match?(product: 'Widget')).must_equal true
|
data/test/namo_test.rb
CHANGED
|
@@ -797,6 +797,129 @@ describe Namo do
|
|
|
797
797
|
end
|
|
798
798
|
end
|
|
799
799
|
|
|
800
|
+
describe "#[]= parameterised formulae" do
|
|
801
|
+
let(:price_data) do
|
|
802
|
+
[
|
|
803
|
+
{symbol: 'AAA', date: 1, close: 10.0, volume: 100},
|
|
804
|
+
{symbol: 'AAA', date: 2, close: 20.0, volume: 200},
|
|
805
|
+
{symbol: 'AAA', date: 3, close: 30.0, volume: 300},
|
|
806
|
+
]
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
let(:prices) do
|
|
810
|
+
Namo.new(price_data)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
# A parameterised moving average: the field and the window length arrive at
|
|
814
|
+
# access time, so one definition serves every column and every period.
|
|
815
|
+
let(:sma) do
|
|
816
|
+
proc do |row, namo, field, period|
|
|
817
|
+
window = namo[symbol: row[:symbol], date: ->(d){d <= row[:date]}].last(period)
|
|
818
|
+
window.sum{|r| r[field]} / window.count.to_f
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
it "resolves with arguments through a yielded Row" do
|
|
823
|
+
prices[:sma] = sma
|
|
824
|
+
_(prices.first[:sma, :close, 2]).must_equal 10.0
|
|
825
|
+
_(prices.last[:sma, :close, 2]).must_equal 25.0
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
it "serves different fields and periods from one definition" do
|
|
829
|
+
prices[:sma] = sma
|
|
830
|
+
_(prices.last[:sma, :close, 3]).must_equal 20.0
|
|
831
|
+
_(prices.last[:sma, :volume, 2]).must_equal 250.0
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
it "resolves inside an Enumerable predicate" do
|
|
835
|
+
prices[:sma] = sma
|
|
836
|
+
result = prices.select{|row| row[:sma, :close, 2] > 12.0}
|
|
837
|
+
_(result).must_be_kind_of Namo
|
|
838
|
+
_(result.values(:date)).must_equal [2, 3]
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
it "lets a one-arity formula reference a parameterised formula with arguments" do
|
|
842
|
+
prices[:sma] = sma
|
|
843
|
+
prices[:rising] = proc{|row| row[:sma, :close, 1] > row[:sma, :close, 3]}
|
|
844
|
+
_(prices.values(:rising)).must_equal [false, true, true]
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
it "lists a parameterised dimension in dimensions and derived_dimensions" do
|
|
848
|
+
prices[:sma] = sma
|
|
849
|
+
_(prices.dimensions).must_equal [:symbol, :date, :close, :volume, :sma]
|
|
850
|
+
_(prices.derived_dimensions).must_equal [:sma]
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
it "omits a parameterised dimension from the no-arg values, coordinates, and to_h" do
|
|
854
|
+
prices[:sma] = sma
|
|
855
|
+
_(prices.values.keys).must_equal [:symbol, :date, :close, :volume]
|
|
856
|
+
_(prices.coordinates.keys).must_equal [:symbol, :date, :close, :volume]
|
|
857
|
+
_(prices.to_h.keys).must_equal [:symbol, :date, :close, :volume]
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
it "raises when values is asked for a parameterised dimension by name" do
|
|
861
|
+
prices[:sma] = sma
|
|
862
|
+
error = _(proc{prices.values(:sma)}).must_raise ArgumentError
|
|
863
|
+
_(error.message).must_equal "wrong number of arguments for :sma (given 0, expected 2)"
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
it "raises when coordinates is asked for a parameterised dimension by name" do
|
|
867
|
+
prices[:sma] = sma
|
|
868
|
+
_(proc{prices.coordinates(:sma)}).must_raise ArgumentError
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
it "raises when a projection names a parameterised dimension" do
|
|
872
|
+
prices[:sma] = sma
|
|
873
|
+
_(proc{prices[:date, :sma]}).must_raise ArgumentError
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
it "raises when a selection selects on a parameterised dimension" do
|
|
877
|
+
prices[:sma] = sma
|
|
878
|
+
_(proc{prices[sma: ->(v){v > 12.0}]}).must_raise ArgumentError
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
it "carries a parameterised formula through contraction" do
|
|
882
|
+
prices[:sma] = sma
|
|
883
|
+
contracted = prices[-:volume]
|
|
884
|
+
_(contracted.derived_dimensions).must_equal [:sma]
|
|
885
|
+
_(contracted.first[:sma, :close, 2]).must_equal 10.0
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
it "carries a parameterised formula through selection, windowing over the filtered rows" do
|
|
889
|
+
prices[:sma] = sma
|
|
890
|
+
filtered = prices[date: 2..3]
|
|
891
|
+
_(filtered.first[:sma, :close, 2]).must_equal 20.0
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
it "materialises through a one-arity wrapper that binds the arguments" do
|
|
895
|
+
prices[:sma] = sma
|
|
896
|
+
prices[:sma_close_2] = proc{|row| row[:sma, :close, 2]}
|
|
897
|
+
_(prices.values(:sma_close_2)).must_equal [10.0, 15.0, 25.0]
|
|
898
|
+
_(prices[:date, :sma_close_2].values(:sma_close_2)).must_equal [10.0, 15.0, 25.0]
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
it "includes a namo-plus-splat formula in the bulk views, called with no extra arguments" do
|
|
902
|
+
prices[:flexible] = proc{|row, namo, *rest| rest.empty? ? namo.count : rest.sum}
|
|
903
|
+
_(prices.values.keys).must_include :flexible
|
|
904
|
+
_(prices.values(:flexible)).must_equal [3, 3, 3]
|
|
905
|
+
_(prices.first[:flexible, 1, 2, 4]).must_equal 7
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
it "carries a parameterised formula through a set-operator result" do
|
|
909
|
+
a = Namo.new(price_data.take(2))
|
|
910
|
+
b = Namo.new([price_data.last])
|
|
911
|
+
a[:sma] = sma
|
|
912
|
+
_((a + b).last[:sma, :close, 2]).must_equal 25.0
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
it "returns [] for a parameterised dimension on an empty Namo without invoking the formula" do
|
|
916
|
+
invoked = false
|
|
917
|
+
empty = Namo.new([], formulae: {sma: ->(row, namo, field, period){invoked = true; 0}})
|
|
918
|
+
_(empty.values(:sma)).must_equal []
|
|
919
|
+
_(invoked).must_equal false
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
800
923
|
describe "data/formula exclusivity" do
|
|
801
924
|
context "projection" do
|
|
802
925
|
let(:price_data) do
|