namo 0.24.0 → 0.25.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 +23 -0
- data/README.md +5 -1
- data/lib/Namo/VERSION.rb +1 -1
- data/lib/namo.rb +15 -1
- data/test/namo_test.rb +64 -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: cf81d723c5723d7737dd544596abbe5b69323f9f027a1aac7caef2cb857ebf5a
|
|
4
|
+
data.tar.gz: 6b1130eb34b16920641e594b52aaff58e9f275ac2a26b0089b21f62a6d8f6630
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb7c989aeb34877ed56e83190bf947217370103420c6f5e5186a88316217f35c4c801d0ed1864bd0105129d3391c99a635ed756c1050bb57a6ce33c4fbffa0e5
|
|
7
|
+
data.tar.gz: da0f8fd20ccfa3f33b860059f33a8e65ddfed9bc157c02c1dd73389c3b907b8f4dbe82eeaf89f6a5e28a45274b74d8d03121ea7bdee9855d772242e4c50f8683
|
data/CHANGELOG
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
_________
|
|
3
3
|
|
|
4
|
+
20260703
|
|
5
|
+
0.25.0: + Namo#attach! — attach a formulary, evicting any data column whose name a formulary method collides with, rather than raising.
|
|
6
|
+
|
|
7
|
+
attach! evicts the colliding data columns first (as []='s proc-over-data assignment does), then attaches — the bang is the explicit consent that attach's raise withholds by default. << stays guarded: a Module still routes to attach.
|
|
8
|
+
|
|
9
|
+
1. ~ Namo#attach — extract the collision set into a new private attach_collisions(modul) (modul.public_instance_methods(false) & data_dimensions); behaviour unchanged, still raises ArgumentError naming the collisions when the set is non-empty.
|
|
10
|
+
2, + Namo#attach! — evicts each colliding data column (@data.each{|row| row.delete(name)} per colliding name, the column membership already confirmed by attach_collisions so no key-guard is needed), then forwards to @formulae.attach and returns self. The eviction mirrors []='s proc-branch clear. + private attach_collisions(modul). << is unchanged: a Module still routes to attach (the guarded form), so the operator stays safe and attach! is reached only by explicit call — one safe operator, one explicit forceful method, matching []= having no operator shortcut for its own eviction.
|
|
11
|
+
3. ~ test/namo_test.rb: + an "#attach!" describe — attach! attaches as attach does for a non-colliding module (returns self, methods resolve); attach! evicts a colliding data column and registers the formulary method in its place, with all access paths (values, Row, selection) agreeing on the formula afterward and the name appearing once in derived_dimensions, not in data_dimensions; attach! over several colliding columns evicts all of them; attach! on a vanilla collision-free module leaves the data untouched; the existing attach collision-raise tests are unchanged, pinning that the guarded form still raises where attach! evicts.
|
|
12
|
+
4. ~ README.md: ~ the Formularies section — + attach! as the forceful sibling of attach, with the materialised-snapshot refresh as the motivating case and the bang-is-consent rationale; + the self-eviction-recurses caveat (a formulary method that reads a column its own name evicts recurses on access, exclusive storage leaving no shadowed value behind the formula — the one way attach!'s eviction differs in feel from []='s); note that << stays the guarded form.
|
|
13
|
+
5. ~ ROADMAP.md: + the 0.25.0 release entry; current-state bumped to 0.25.0.
|
|
14
|
+
6. ~ Namo::VERSION: /0.24.1/0.25.0/
|
|
15
|
+
|
|
16
|
+
20260629
|
|
17
|
+
0.24.1: ~ Namo#add_row — guard row-append against a data/formula name collision.
|
|
18
|
+
|
|
19
|
+
0.24.0 left row-append (the Hash/Row arms of <<) unguarded, on the reasoning that the data/formula exclusivity invariant was []='s to keep. That reasoning had a hole: when an appended row carries a key matching an existing formula name and lands as @data.first (e.g. the first row streamed into an empty formulary-bearing Namo), bulk values reads the raw datum (data_dimensions keys off @data.first.keys) while per-Row access derives the formula (Row#[] checks @formulae first) — the two access paths disagree, the precise split the invariant exists to prevent. So add_row now raises rather than admitting the collision, symmetric with attach's guard: a row is a bulk append you didn't name column-by-column, so refusing is safer than []='s call-site eviction.
|
|
20
|
+
|
|
21
|
+
1. ~ lib/namo.rb: ~ private add_row(row) — + a guard: collisions = row.keys & derived_dimensions; raise ArgumentError naming them unless empty, before appending. A non-colliding row appends and returns self as before. << is otherwise unchanged: Module→attach, Row→add_row(row.to_h), Hash→add_row(row), else→TypeError.
|
|
22
|
+
2. ~ test/namo_test.rb: + the "#<<" describe gains two guard cases — an appended Hash whose keys collide with a formula raises ArgumentError naming the collision, and an appended Row likewise. The existing non-colliding row/chain tests are unchanged.
|
|
23
|
+
3. ~ README.md: ~ the Formularies << note — the row arm now states the guard (a row colliding with a formula name raises, symmetric with attach), replacing the 0.24.0 "surfaces live per existing precedence" wording, which held only when the colliding key was not @data.first.
|
|
24
|
+
4. ~ ROADMAP.md: + the 0.24.1 release entry; current-state bumped to 0.24.1; the 0.24.0 "not guarded" note annotated as guarded at 0.24.1.
|
|
25
|
+
5. ~ Namo::VERSION: /0.24.0/0.24.1/
|
|
26
|
+
|
|
4
27
|
20260629
|
|
5
28
|
0.24.0: ~ Namo#<<, Namo::Collection#<< — widen from a formulary-only alias into a polymorphic "append a constituent" operator.
|
|
6
29
|
|
data/README.md
CHANGED
|
@@ -751,7 +751,7 @@ flows.values(:cum_delta) # => [20, -20, -20]
|
|
|
751
751
|
flows.derived_dimensions # => [:signed_volume, :cum_delta]
|
|
752
752
|
```
|
|
753
753
|
|
|
754
|
-
`<<` appends a *constituent*. For a base Namo the constituents are formularies and rows: a **module** attaches (the operator form of `attach`, so `flows << OrderFlow` reads the same as `flows.attach(OrderFlow)`); a **Hash** appends as a data row; a **Row** (one drawn from another Namo) appends its underlying hash as a row. It returns the Namo, so the arms chain freely: `flows << OrderFlow << {date: 4, buys: 10, sells: 5} << Scoring`. Two things `<<` deliberately does not take: a bare callable (formula *registration* stays with `[]=`, which dispatches callable-vs-scalar and enforces data/formula exclusivity) and a whole Namo (combining collections is `+`'s job) — both raise. A bare Hash is therefore always a row, never a body of formulae. Appending a row
|
|
754
|
+
`<<` appends a *constituent*. For a base Namo the constituents are formularies and rows: a **module** attaches (the operator form of `attach`, so `flows << OrderFlow` reads the same as `flows.attach(OrderFlow)`); a **Hash** appends as a data row; a **Row** (one drawn from another Namo) appends its underlying hash as a row. It returns the Namo, so the arms chain freely: `flows << OrderFlow << {date: 4, buys: 10, sells: 5} << Scoring`. Two things `<<` deliberately does not take: a bare callable (formula *registration* stays with `[]=`, which dispatches callable-vs-scalar and enforces data/formula exclusivity) and a whole Namo (combining collections is `+`'s job) — both raise. A bare Hash is therefore always a row, never a body of formulae. Appending a row whose keys collide with an existing formula name **raises** an `ArgumentError` naming the collision — a name is data or derived, never both. The guard is symmetric with `attach`'s: `[]=` names one column at the call site, so its scalar form evicts a same-named formula as your explicit intent, but a row is a bulk append you didn't name column-by-column, so the safe response is to refuse rather than let data and a formula of the same name disagree on access. Contract or rename first, then append.
|
|
755
755
|
|
|
756
756
|
A module brands itself a formulary by including `Namo::Formulary`. The marker is mandatory — `attach` raises `ArgumentError` for an untagged module, so an ordinary module of helpers can't be mistaken for a body of derivations. Within a formulary, the public methods are the derivations and `private` methods are helpers: `net` above never appears in `derived_dimensions` and never resolves as a dimension. The separation needs no per-method declaration — public or private says it.
|
|
757
757
|
|
|
@@ -768,6 +768,10 @@ Because each method is copied into the store, the rules already governing `[]=`
|
|
|
768
768
|
- **Materialise-and-drop on projection.** Naming a formulary method in a projection snapshots its values into a data column *and drops the formula*, exactly as for a `[]=` formula: `flows[:date, :signed_volume]` returns a Namo reporting `:signed_volume` as data, not derived, with all access paths agreeing on the stored snapshot.
|
|
769
769
|
- **Carry-through.** Selection, the set and composition operators, and a projection that *omits* the name all keep the formula live, computing against the result's own rows — the carry-through is free, because the formulae are ordinary store entries.
|
|
770
770
|
|
|
771
|
+
`attach!` is the forceful sibling of `attach`. Where `attach` refuses a data collision, `attach!` **evicts** the colliding data columns first — deleting each one exactly as assigning a proc through `[]=` clears a same-named data column — then attaches. The bang is consent: `attach` won't destroy a column you never named, but `attach!` is you saying you meant to. The motivating case is refreshing a formulary over a *materialised snapshot* — once a derived dimension has been projected into a stored column (materialise-and-drop, above), re-attaching the formulary that defined it collides with the now-data column, and `attach!` re-attaches over it in one step where `attach` would require you to contract the column away first. `<<` stays the guarded form: a module through `<<` routes to `attach`, so the operator never evicts — only an explicit `attach!` call does.
|
|
772
|
+
|
|
773
|
+
Note that there is no counterpart in `[]=`'s scalar-over-formula eviction and is worth naming: a formulary method that reads a column its own name evicts will **recurse** on access. The eviction removes the datum, and exclusive storage leaves no shadowed value behind the formula, so `Row#[]` re-enters the formula rather than falling back to data — a `def close(row); row[:close] * ...; end` attached with `attach!` over a `:close` data column has no `:close` data left to read. `[]=` rarely bites this way because you write the proc at the call site, beside the name it replaces; a formulary is authored elsewhere, so a method reading its own evicted column is easy to attach without noticing. Self-reference is unguarded here as everywhere in the formula mechanism — keep a formulary's inputs and outputs under distinct names, or `attach` (not `attach!`) and contract deliberately.
|
|
774
|
+
|
|
771
775
|
Two Namos that expose the same names — one via `[]=`, one via a formulary — are `===`; a Namo with a formulary attached is not `===` to a vanilla Namo of the same data dimensions.
|
|
772
776
|
|
|
773
777
|
Formulary methods take `row` explicitly and index it (`row[:buys]`), the same as `[]=` formulae. Resolving bare names inside a method body (`buys` for `row[:buys]`) and memoising derived values on a frozen Namo arrive in a later release; until then a formulary method is a plain function of its `row` (and `namo`).
|
data/lib/Namo/VERSION.rb
CHANGED
data/lib/namo.rb
CHANGED
|
@@ -89,7 +89,7 @@ class Namo
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def attach(modul)
|
|
92
|
-
collisions = modul
|
|
92
|
+
collisions = attach_collisions(modul)
|
|
93
93
|
unless collisions.empty?
|
|
94
94
|
raise ArgumentError, "formulary methods collide with data dimensions: #{collisions.inspect}"
|
|
95
95
|
end
|
|
@@ -97,6 +97,12 @@ class Namo
|
|
|
97
97
|
self
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
def attach!(modul)
|
|
101
|
+
attach_collisions(modul).each{|name| @data.each{|row| row.delete(name)}}
|
|
102
|
+
@formulae.attach(modul)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
100
106
|
def <<(constituent)
|
|
101
107
|
case constituent
|
|
102
108
|
when Module then attach(constituent)
|
|
@@ -260,10 +266,18 @@ class Namo
|
|
|
260
266
|
end
|
|
261
267
|
|
|
262
268
|
def add_row(row)
|
|
269
|
+
collisions = row.keys & derived_dimensions
|
|
270
|
+
unless collisions.empty?
|
|
271
|
+
raise ArgumentError, "row keys collide with formulae: #{collisions.inspect}"
|
|
272
|
+
end
|
|
263
273
|
@data << row
|
|
264
274
|
self
|
|
265
275
|
end
|
|
266
276
|
|
|
277
|
+
def attach_collisions(modul)
|
|
278
|
+
modul.public_instance_methods(false) & data_dimensions
|
|
279
|
+
end
|
|
280
|
+
|
|
267
281
|
def attach_included_formularies
|
|
268
282
|
self.class.ancestors.reverse.each do |modul|
|
|
269
283
|
next if modul.is_a?(Class) || !modul.include?(Namo::Formulary)
|
data/test/namo_test.rb
CHANGED
|
@@ -747,6 +747,58 @@ describe Namo do
|
|
|
747
747
|
end
|
|
748
748
|
end
|
|
749
749
|
|
|
750
|
+
describe "#attach!" do
|
|
751
|
+
it "attaches as attach does for a non-colliding module" do
|
|
752
|
+
namo = Namo.new(flow_data)
|
|
753
|
+
_(namo.attach!(delta)).must_be_same_as namo
|
|
754
|
+
_(namo.values(:signed_volume)).must_equal [20, -40, 0]
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
it "evicts a colliding data column and registers the formulary method in its place" do
|
|
758
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
759
|
+
widened.attach!(delta)
|
|
760
|
+
_(widened.data_dimensions).wont_include :signed_volume
|
|
761
|
+
_(widened.derived_dimensions).must_include :signed_volume
|
|
762
|
+
_(widened.dimensions.count(:signed_volume)).must_equal 1
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "agrees across all access paths on the evicted-then-derived name" do
|
|
766
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
767
|
+
widened.attach!(delta)
|
|
768
|
+
_(widened.values(:signed_volume)).must_equal [20, -40, 0]
|
|
769
|
+
_(widened.entries.first[:signed_volume]).must_equal 20
|
|
770
|
+
_(widened[signed_volume: 20].values(:date)).must_equal [1]
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
it "evicts every colliding column when several collide" do
|
|
774
|
+
clash = Module.new do
|
|
775
|
+
include Namo::Formulary
|
|
776
|
+
def buys(row); row[:date] * 2; end
|
|
777
|
+
def sells(row); row[:date] * 3; end
|
|
778
|
+
end
|
|
779
|
+
namo = Namo.new(flow_data)
|
|
780
|
+
namo.attach!(clash)
|
|
781
|
+
_(namo.data_dimensions).wont_include :buys
|
|
782
|
+
_(namo.data_dimensions).wont_include :sells
|
|
783
|
+
_(namo.derived_dimensions).must_include :buys
|
|
784
|
+
_(namo.derived_dimensions).must_include :sells
|
|
785
|
+
_(namo.values(:buys)).must_equal [2, 4, 6]
|
|
786
|
+
_(namo.values(:sells)).must_equal [3, 6, 9]
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
it "leaves the data untouched when the module does not collide" do
|
|
790
|
+
namo = Namo.new(flow_data)
|
|
791
|
+
namo.attach!(delta)
|
|
792
|
+
_(namo.values(:buys)).must_equal [60, 80, 75]
|
|
793
|
+
_(namo.values(:sells)).must_equal [40, 120, 75]
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
it "raises through attach (the guarded form) where attach! evicts" do
|
|
797
|
+
widened = Namo.new(flow_data.map{|row| row.merge(signed_volume: 999)})
|
|
798
|
+
_(proc{widened.attach(delta)}).must_raise ArgumentError
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
750
802
|
describe "#<<" do
|
|
751
803
|
it "attaches a formulary, the same as #attach" do
|
|
752
804
|
namo = Namo.new(flow_data)
|
|
@@ -794,6 +846,18 @@ describe Namo do
|
|
|
794
846
|
_(namo.values(:date)).must_equal [1, 2, 3, 4, 5]
|
|
795
847
|
_(namo.values(:signed_volume)).must_equal [20, -40, 0, 5, 0]
|
|
796
848
|
end
|
|
849
|
+
|
|
850
|
+
it "raises an ArgumentError when an appended Hash's keys collide with a formula" do
|
|
851
|
+
namo = Namo.new(flow_data).attach(delta)
|
|
852
|
+
error = _(proc{namo << {date: 4, buys: 10, sells: 5, signed_volume: 999}}).must_raise ArgumentError
|
|
853
|
+
_(error.message).must_match(/signed_volume/)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
it "raises likewise when an appended Row's keys collide with a formula" do
|
|
857
|
+
namo = Namo.new(flow_data).attach(delta)
|
|
858
|
+
row = Namo.new([{date: 4, buys: 10, sells: 5, signed_volume: 999}]).entries.first
|
|
859
|
+
_(proc{namo << row}).must_raise ArgumentError
|
|
860
|
+
end
|
|
797
861
|
end
|
|
798
862
|
|
|
799
863
|
describe "#derived_dimensions" do
|