namo 0.28.0 → 0.29.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf138a52a691a4d50f093e506e5018d652a66ab23846593e2622cbe23710c8ef
4
- data.tar.gz: a8bbdc8cdf8af2b8c3df9881d468cc0445ffca7dd1f4523c614a2e71a8acc4c5
3
+ metadata.gz: 5e56f3159063720b0e933e9c346ddad824831a51dac7a576da6d0cf841fd28ea
4
+ data.tar.gz: 8aee98bb89f8c7ab978c0ce459cc90c8259123123c13f5158a3e16dbb93922c7
5
5
  SHA512:
6
- metadata.gz: 58c3cb00aee14efb44155a9a4a8e5eaa748aa51389f0ac19c27f4d8a077970cac3b605e7d1ffad2c95329782fdf04755ee1e8975b9e2d34209705a1e8f780014
7
- data.tar.gz: 7492096f83494f4c5a976b602796e3611c4eef1e920cc6b6179ff1b63e7f7fcd7a7f1e12ab2d637d3a1ef8fb4a0eeeb0364a0068ff3cbe12332b8e4b0e0d20c2
6
+ metadata.gz: 16e3d16921ee88ae7d5d76c79227efb035128ac46f17d93fa61c7445434bb1c475c597ff7e2478fe19bbaf8f53b73ed366fee5ed1e514ad99a423a030db0d46c
7
+ data.tar.gz: 7e0045e6e6a842be576e171c9c31bde901f7ade581e5dce694e853a868f832c358e5eb7329ce9fc14efdc13a20663f4877135895f99328d646bb8717d177a2af
data/CHANGELOG CHANGED
@@ -1,6 +1,21 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 20260711
3
+ ## 20260715
4
+
5
+ 0.29.0: Per access indexed materialisation.
6
+
7
+ Materialise each column once per top-level access, collapsing an O(n²) parent-scanning formula to O(n), with no value retained across accesses.
8
+
9
+ 1. ~ Namo#values: wrap the body in a private materialising scope.
10
+ 2. ~ Namo#values_for: memoise its column build into @materialised[dim] ||= (the data/derived branch that was its body).
11
+ 3. + Namo#materialising (a per-access scope that sets @materialised, yields, and clears it in an ensure; a nested values call reuses the open scope rather than starting another). A formula that scans its parent asks for the same column once per row it visits; the scope materialises that column once and reuses it for the rest of the access, then discards it — so every access still recomputes from current state and nothing can go stale. Enumerable is untouched: iteration paths (each/select/map/group_by) do not yet open the scope.
12
+ 4. ~ test/namo_test.rb: + an "access-scoped materialisation" describe — a referenced column materialises once per access, not once per referencing row; nothing is retained across accesses (a later access recomputes from current state); the scope clears on a raising materialisation, leaving later accesses working.
13
+ 5. ~ README.md: ~ the cross-row liveness note — "with no caching" becomes "with no caching across accesses", the within-access materialisation being a loop-invariant hoist, not a cache.
14
+ 6. ~ ROADMAP.md: + the 0.29.0 entry; current-state bumped to 0.29.0; the 1.x pure-live passages refined from "no memoisation anywhere" to the invariant "no derived value outlives the access that produced it"; the Live-data-and-cache-invalidation open question gains the access-scoped resolution; + forward notes on streaming performance vs incremental maintenance (a commercial-tier candidate) and on formula opacity as the shared wall behind ===, dependency-aware projection, serialisation, and incremental streaming.
15
+ 7. ~ docs/: regenerate the print-ready PDFs.
16
+ 8. ~ Namo::VERSION: /0.28.0/0.29.0/
17
+
18
+ ## 20260713
4
19
 
5
20
  0.28.0: ~ Namo::Collection#detail/#as_detail: Accept the by label positionally or by keyword.
6
21
 
data/README.md CHANGED
@@ -688,7 +688,7 @@ prices.values(:sma)
688
688
  # => [10.0, 15.0, 20.0]
689
689
  ```
690
690
 
691
- `namo` is the Namo that yielded the row, live — so the window always reflects the current state of the object you ask through. A filtered Namo's rows window over the filtered rows; an operator result's rows window over the result. Appending a row changes every cross-row value on the next access, with no caching.
691
+ `namo` is the Namo that yielded the row, live — so the window always reflects the current state of the object you ask through. A filtered Namo's rows window over the filtered rows; an operator result's rows window over the result. Appending a row changes every cross-row value on the next access, with no caching across accesses.
692
692
 
693
693
  One-arity formulae are unchanged, and the two forms mix freely — a one-arity formula can reference a two-arity one, and a two-arity formula can reference a one-arity one, by name.
694
694
 
data/lib/Namo/VERSION.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Namo::VERSION
3
3
 
4
4
  class Namo
5
- VERSION = '0.28.0'
5
+ VERSION = '0.29.0'
6
6
  end
data/lib/namo.rb CHANGED
@@ -30,12 +30,14 @@ class Namo
30
30
  end
31
31
 
32
32
  def values(*dims)
33
- if dims.empty?
34
- materialisable_dimensions.each_with_object({}){|dim, hash| hash[dim] = values_for(dim)}
35
- elsif dims.length == 1
36
- values_for(dims.first)
37
- else
38
- dims.each_with_object({}){|dim, hash| hash[dim] = values_for(dim)}
33
+ materialising do
34
+ if dims.empty?
35
+ materialisable_dimensions.each_with_object({}){|dim, hash| hash[dim] = values_for(dim)}
36
+ elsif dims.length == 1
37
+ values_for(dims.first)
38
+ else
39
+ dims.each_with_object({}){|dim, hash| hash[dim] = values_for(dim)}
40
+ end
39
41
  end
40
42
  end
41
43
 
@@ -290,14 +292,26 @@ class Namo
290
292
  end
291
293
  end
292
294
 
293
- def values_for(dim)
294
- if data_dimensions.include?(dim)
295
- @data.map{|row_data| row_data[dim]}
296
- else
297
- @data.map{|row_data| Row.new(row_data, @formulae, self)[dim]}
295
+ def materialising
296
+ return yield if @materialised
297
+ @materialised = {}
298
+ begin
299
+ yield
300
+ ensure
301
+ @materialised = nil
298
302
  end
299
303
  end
300
304
 
305
+ def values_for(dim)
306
+ @materialised[dim] ||= (
307
+ if data_dimensions.include?(dim)
308
+ @data.map{|row_data| row_data[dim]}
309
+ else
310
+ @data.map{|row_data| Row.new(row_data, @formulae, self)[dim]}
311
+ end
312
+ )
313
+ end
314
+
301
315
  def materialisable_dimensions
302
316
  dimensions.reject{|dim| requires_arguments?(dim)}
303
317
  end
data/test/namo_test.rb CHANGED
@@ -287,6 +287,44 @@ describe Namo do
287
287
  end
288
288
  end
289
289
 
290
+ describe "access-scoped materialisation" do
291
+ it "materialises a referenced column once per access, not once per referencing row" do
292
+ calls = Hash.new(0)
293
+ namo = Namo.new([{n: 1}, {n: 2}, {n: 3}])
294
+ namo[:base] = proc{|row| calls[:base] += 1; row[:n] * 10}
295
+ namo[:sum_base] = proc{|row, namo| namo.values(:base).sum}
296
+ _(namo.values(:sum_base)).must_equal [60, 60, 60]
297
+ _(calls[:base]).must_equal 3
298
+ end
299
+
300
+ it "retains nothing across accesses — a later access recomputes from current state" do
301
+ namo = Namo.new([{n: 1}, {n: 2}])
302
+ namo[:total] = proc{|row, namo| namo.values(:n).sum}
303
+ _(namo.values(:total)).must_equal [3, 3]
304
+ namo.data << {n: 4}
305
+ _(namo.values(:total)).must_equal [7, 7, 7]
306
+ end
307
+
308
+ it "clears the access scope when materialisation raises, leaving later accesses working" do
309
+ namo = Namo.new([{n: 1}])
310
+ namo[:boom] = proc{|row| raise 'materialisation blew up'}
311
+ _(->{namo.values(:boom)}).must_raise RuntimeError
312
+ namo[:ok] = proc{|row| row[:n]}
313
+ _(namo.values(:ok)).must_equal [1]
314
+ end
315
+
316
+ it "keeps one scope when a formula's nested values call runs during a multi-dimension access" do
317
+ calls = 0
318
+ namo = Namo.new([{x: 1}, {x: 2}, {x: 3}])
319
+ namo[:base] = proc{|row| calls += 1; row[:x] * 2}
320
+ namo[:agg] = proc{|row, namo| namo.values(:base).first}
321
+ # :agg's formula calls values(:base) mid-materialisation; that nested call must
322
+ # reuse the open scope, not reset it, so the :x column that follows still builds.
323
+ _(namo.values(:agg, :x)).must_equal(agg: [2, 2, 2], x: [1, 2, 3])
324
+ _(calls).must_equal 3
325
+ end
326
+ end
327
+
290
328
  describe "#to_h" do
291
329
  it "returns the full values Hash" do
292
330
  _(sales.to_h).must_equal sales.values
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: namo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran