namo 0.26.0 → 0.26.1
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 +11 -0
- data/README.md +12 -2
- data/lib/Namo/Formulae.rb +16 -10
- data/lib/Namo/VERSION.rb +1 -1
- data/test/Namo/Formulae_test.rb +35 -3
- data/test/namo_test.rb +37 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40ae1191c5889356a1f8442673b3f44588521aa07f3f379fe58c9afa85b574c8
|
|
4
|
+
data.tar.gz: c38c7996e0a31e6050adcd9162dbbd6bde3b1c8b12cbaf0b1ab6a7848eeffb78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bf0fb1cf44efc3096388e5adda7503802003e3b49a6539404cefed47ea03af1ca2414089e2599d0d7e0e1676f1a674f3bfadc80131ca4c88401d8c87d38b4c7
|
|
7
|
+
data.tar.gz: f8ea345eee0bca6cf22ea50f470874becce72795649a0e28bba8df3027c70511634accf961f61aa0647177214e18ad139bb24a5870a534ec9e0de096023b67bc
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20260710
|
|
4
|
+
|
|
5
|
+
0.26.1: Refactor Namo::Formulae#attach through a private #bind, and document mutability and the corrected collision-resolution idioms.
|
|
6
|
+
|
|
7
|
+
1. ~ lib/Namo/Formulae.rb — attach binds each of a formulary's public method names through a new private bind(name, modul): marker guard, host.extend(modul), then self[name] = modul.instance_method(name).bind(host). attach and detach share one private raise_unless_formulary marker guard. bind stays private — attach is its only caller.
|
|
8
|
+
2. ~ README.md: + a "Mutability" section (Namos are mutable; an unfrozen Namo is not a reliable Hash key or Set member since hash tracks data and formula names; the deliberate attr_accessor :data back door and the sanctioned namo << row append idiom); + a private-helper support note under Formularies (a public formulary method resolves its private helpers through the shared host; a same-named helper in a later-attached formulary shadows the earlier one); ~ the collision-resolution passages — correct a prescribed rename operation Namo does not have to the shipped idioms (detach, formula-plus-projection, re-key before ingestion, attach!).
|
|
9
|
+
3. ~ ROADMAP.md: + a note in the deep-freeze open question — the attr_accessor :data back door means a generation counter cannot observe all mutation, deciding the 2.x caching mechanism in deep freeze's favour.
|
|
10
|
+
4. ~ test/Namo/Formulae_test.rb, test/namo_test.rb: identity assertions on stored callables migrate to behavioural ones; + a "private support methods" describe (a public formulary method resolving through its private helper; shared-host same-named-helper shadowing).
|
|
11
|
+
5. ~ docs/: regenerate the print-ready PDFs.
|
|
12
|
+
6. ~ Namo::VERSION: /0.26.0/0.26.1/
|
|
13
|
+
|
|
3
14
|
## 20260704
|
|
4
15
|
|
|
5
16
|
0.26.0: + Namo#detach, Namo::Formulae#detach — remove a formula by name or a formulary's methods by module, completing the attach/attach!/detach family.
|
data/README.md
CHANGED
|
@@ -599,6 +599,14 @@ one < two
|
|
|
599
599
|
|
|
600
600
|
The dimensions must match; different dimensions raise an `ArgumentError`. Comparing against a non-Namo raises a `TypeError`.
|
|
601
601
|
|
|
602
|
+
### Mutability
|
|
603
|
+
|
|
604
|
+
Namos are mutable. Rows append (`<<`), formulae register and remove (`[]=`, `attach`, `detach`), and every view recomputes from current state on the next access — mutability is what the live computation model runs on.
|
|
605
|
+
|
|
606
|
+
The consequence for the equality machinery above: because `hash` is computed from data and formula names, mutation shifts it, so an *unfrozen* Namo is not a reliable Hash key or Set member — a lookup can miss the entry the Namo was stored under. Freeze marks the transition: mutate during exploration, `freeze` before using a Namo for hash-keyed lookup or sharing it across threads. Enforcement — a deep freeze that propagates to `@data` and `@formulae` — arrives with the 2.x performance phase, where freeze also gates caching; until then `freeze` is Ruby's shallow default and the lifecycle discipline is the user's.
|
|
607
|
+
|
|
608
|
+
One back door is deliberate: `data` is a public accessor, so `namo.data << row` and whole-array replacement are possible. This is the same trust the constructor extends — Namo takes your array of hashes without copying or validating it — kept open for the surrounding system that owns the data feed. The sanctioned append idiom remains `namo << row`, which guards against data/formula name collisions where the accessor bypasses every guard.
|
|
609
|
+
|
|
602
610
|
### Formulae
|
|
603
611
|
|
|
604
612
|
Define computed dimensions using `[]=`:
|
|
@@ -757,10 +765,12 @@ flows.values(:cum_delta) # => [20, -20, -20]
|
|
|
757
765
|
flows.derived_dimensions # => [:signed_volume, :cum_delta]
|
|
758
766
|
```
|
|
759
767
|
|
|
760
|
-
`<<` 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.
|
|
768
|
+
`<<` 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. Detach the formula (`namo.detach(:signed_volume)`) or re-key the incoming row first, then append.
|
|
761
769
|
|
|
762
770
|
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.
|
|
763
771
|
|
|
772
|
+
Private helpers remain fully *usable* in their support role: `attach` extends the whole module onto the internal host, so a public formulary method calling a helper resolves it through ordinary method lookup — only the store, and therefore the dimension namespace, is restricted to the public tier. One caveat of the single shared host: attached formularies share one method-resolution scope, so if two formularies define a *same-named* private helper, the most recently attached one wins — even for calls made from the earlier formulary's own methods. Give helpers distinctive names, or re-attach the formulary whose helper should prevail.
|
|
773
|
+
|
|
764
774
|
Attaching **copies** the formulary's public methods into the Namo's formula collection — each becomes a stored formula, indistinguishable from one assigned through `[]=`. From there they are `[]=` formulae in everything but how they were defined: they resolve through the same path, carry through the same operators, and obey the same exclusivity rule.
|
|
765
775
|
|
|
766
776
|
The copy is a *snapshot* taken at the moment of attachment, and this is where a formulary departs from ordinary Ruby. A real `include` (or `extend`) is a live link: its methods resolve through the ancestor chain, so a method added to the module later, a redefinition, or a further module mixed in afterwards are all seen by objects that already include it. `attach` forgoes that — it copies the method set once, so later changes to the module are not reflected on an already-attached Namo. Re-attach to pick them up. (A live-link mechanism may come in a later release; it is not in 0.23.0.)
|
|
@@ -770,7 +780,7 @@ The copy is a *snapshot* taken at the moment of attachment, and this is where a
|
|
|
770
780
|
Because each method is copied into the store, the rules already governing `[]=` apply:
|
|
771
781
|
|
|
772
782
|
- **Most-recent wins.** Attaching a second formulary that defines a colliding name overwrites the first, exactly as a second `[]=` would. Equally, `[]=` and `attach` interleave as plain last-write-wins on a shared name — whichever was applied last governs.
|
|
773
|
-
- **Data collisions raise.** A name is data or derived, never both. If a formulary method's name collides with an existing data column, attaching raises an `ArgumentError` naming the collision rather than silently destroying the data. The reason it raises where `[]=` silently clears: `[]=` names one column at the call site, so replacing it is your explicit intent; a formulary names a whole *module*, so a data collision is a name you never typed — and possibly several columns at once — that you most likely didn't mean to destroy. The same raise guards the class-`include` channel, where it fires at construction. Resolve it explicitly first
|
|
783
|
+
- **Data collisions raise.** A name is data or derived, never both. If a formulary method's name collides with an existing data column, attaching raises an `ArgumentError` naming the collision rather than silently destroying the data. The reason it raises where `[]=` silently clears: `[]=` names one column at the call site, so replacing it is your explicit intent; a formulary names a whole *module*, so a data collision is a name you never typed — and possibly several columns at once — that you most likely didn't mean to destroy. The same raise guards the class-`include` channel, where it fires at construction. Resolve it explicitly first, then attach: contract the column away (`namo[-:signed_volume]`); or move it to a new name by formula-plus-projection — register the new name as a formula reading the old (`namo[:raw_signed_volume] = proc{|r| r[:signed_volume]}`) and project the kept dimensions plus the new name, which materialises it as a data column and drops the formula; or re-key the rows before ingestion. `attach!` is the fourth resolution — consenting to the eviction instead.
|
|
774
784
|
- **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.
|
|
775
785
|
- **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.
|
|
776
786
|
|
data/lib/Namo/Formulae.rb
CHANGED
|
@@ -14,13 +14,8 @@ class Namo
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def attach(modul)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
end
|
|
20
|
-
host.extend(modul)
|
|
21
|
-
modul.public_instance_methods(false).each do |name|
|
|
22
|
-
@store[name] = host.method(name)
|
|
23
|
-
end
|
|
17
|
+
raise_unless_formulary(modul)
|
|
18
|
+
modul.public_instance_methods(false).each{|name| bind(name, modul)}
|
|
24
19
|
self
|
|
25
20
|
end
|
|
26
21
|
alias_method :<<, :attach
|
|
@@ -30,9 +25,7 @@ class Namo
|
|
|
30
25
|
when Symbol
|
|
31
26
|
@store.delete(constituent)
|
|
32
27
|
when Module
|
|
33
|
-
|
|
34
|
-
raise ArgumentError, "not a Namo::Formulary: #{constituent}"
|
|
35
|
-
end
|
|
28
|
+
raise_unless_formulary(constituent)
|
|
36
29
|
constituent.public_instance_methods(false).each{|name| @store.delete(name)}
|
|
37
30
|
else
|
|
38
31
|
raise TypeError, "can't detach #{constituent.class} from a Formulae; expected a Symbol or a Module (formulary)"
|
|
@@ -114,6 +107,19 @@ class Namo
|
|
|
114
107
|
@host ||= Object.new
|
|
115
108
|
end
|
|
116
109
|
|
|
110
|
+
def bind(name, modul)
|
|
111
|
+
raise_unless_formulary(modul)
|
|
112
|
+
host.extend(modul)
|
|
113
|
+
self[name] = modul.instance_method(name).bind(host)
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def raise_unless_formulary(modul)
|
|
118
|
+
unless modul.include?(Namo::Formulary)
|
|
119
|
+
raise ArgumentError, "not a Namo::Formulary: #{modul}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
117
123
|
def collection_scoped?(name)
|
|
118
124
|
required_parameter_count(name) >= 2
|
|
119
125
|
end
|
data/lib/Namo/VERSION.rb
CHANGED
data/test/Namo/Formulae_test.rb
CHANGED
|
@@ -111,7 +111,8 @@ describe Namo::Formulae do
|
|
|
111
111
|
it "yields name/callable pairs" do
|
|
112
112
|
yielded = {}
|
|
113
113
|
formulae.each{|name, callable| yielded[name] = callable}
|
|
114
|
-
_(yielded).must_equal
|
|
114
|
+
_(yielded.keys).must_equal [:revenue]
|
|
115
|
+
_(yielded[:revenue].call({price: 10.0, quantity: 100})).must_equal 1000.0
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
it "returns an enumerator with no block" do
|
|
@@ -136,7 +137,7 @@ describe Namo::Formulae do
|
|
|
136
137
|
it "lets the argument win on a name conflict" do
|
|
137
138
|
other = proc{|r| 0}
|
|
138
139
|
merged = formulae.merge(Namo::Formulae.new({revenue: other}))
|
|
139
|
-
_(merged[:revenue]).
|
|
140
|
+
_(merged[:revenue].call({price: 10.0, quantity: 100})).must_equal 0
|
|
140
141
|
end
|
|
141
142
|
|
|
142
143
|
it "does not mutate the receiver" do
|
|
@@ -177,7 +178,9 @@ describe Namo::Formulae do
|
|
|
177
178
|
|
|
178
179
|
it "supports select returning name/callable pairs" do
|
|
179
180
|
formulae[:cost] = cost
|
|
180
|
-
|
|
181
|
+
selected = formulae.select{|name, _| name == :cost}
|
|
182
|
+
_(selected.map(&:first)).must_equal [:cost]
|
|
183
|
+
_(selected.first.last.call({quantity: 100})).must_equal 400.0
|
|
181
184
|
end
|
|
182
185
|
end
|
|
183
186
|
|
|
@@ -207,6 +210,12 @@ describe Namo::Formulae do
|
|
|
207
210
|
it "defaults to an empty store" do
|
|
208
211
|
_(Namo::Formulae.new.keys).must_equal []
|
|
209
212
|
end
|
|
213
|
+
|
|
214
|
+
it "stores a seeded callable as given, resolvable thereafter" do
|
|
215
|
+
formulae = Namo::Formulae.new({revenue: revenue})
|
|
216
|
+
_(formulae[:revenue]).must_be_same_as revenue
|
|
217
|
+
_(formulae.derive(:revenue, {price: 10.0, quantity: 100}, nil)).must_equal 1000.0
|
|
218
|
+
end
|
|
210
219
|
end
|
|
211
220
|
|
|
212
221
|
describe "formulary tier" do
|
|
@@ -258,6 +267,20 @@ describe Namo::Formulae do
|
|
|
258
267
|
formulae = Namo::Formulae.new.attach(delta).attach(other)
|
|
259
268
|
_(formulae[:signed_volume].receiver).must_be_same_as formulae[:momentum].receiver
|
|
260
269
|
end
|
|
270
|
+
|
|
271
|
+
it "stores a formulary's methods as Methods owned by the module, bound to the shared host" do
|
|
272
|
+
_(attached[:signed_volume]).must_be_kind_of Method
|
|
273
|
+
_(attached[:signed_volume].owner).must_be_same_as delta
|
|
274
|
+
_(attached.derive(:signed_volume, {buys: 60, sells: 40}, nil)).must_equal 20
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it "lets a []= overwrite take effect after an anonymous formulary attached the name" do
|
|
278
|
+
formulae = Namo::Formulae.new
|
|
279
|
+
formulae[:signed_volume] = proc{|r| 1}
|
|
280
|
+
formulae.attach(delta)
|
|
281
|
+
formulae[:signed_volume] = proc{|r| 0}
|
|
282
|
+
_(formulae.derive(:signed_volume, {buys: 60, sells: 40}, nil)).must_equal 0
|
|
283
|
+
end
|
|
261
284
|
end
|
|
262
285
|
|
|
263
286
|
describe "#<<" do
|
|
@@ -328,6 +351,15 @@ describe Namo::Formulae do
|
|
|
328
351
|
formulae = Namo::Formulae.new.attach(delta).attach(alt)
|
|
329
352
|
_(formulae.derive(:signed_volume, {buys: 60, sells: 40}, nil)).must_equal 999
|
|
330
353
|
end
|
|
354
|
+
|
|
355
|
+
it "brings a shadowed formulary's definition back when it is re-attached (binds the module's own method, not the shared host's ancestry order)" do
|
|
356
|
+
alt = Module.new do
|
|
357
|
+
include Namo::Formulary
|
|
358
|
+
def signed_volume(row); 999; end
|
|
359
|
+
end
|
|
360
|
+
formulae = Namo::Formulae.new.attach(delta).attach(alt).attach(delta)
|
|
361
|
+
_(formulae.derive(:signed_volume, {buys: 60, sells: 40}, nil)).must_equal 20
|
|
362
|
+
end
|
|
331
363
|
end
|
|
332
364
|
|
|
333
365
|
describe "#detach" do
|
data/test/namo_test.rb
CHANGED
|
@@ -3,6 +3,13 @@ require 'minitest-spec-context'
|
|
|
3
3
|
|
|
4
4
|
require_relative '../lib/namo'
|
|
5
5
|
|
|
6
|
+
module SupportedFlow
|
|
7
|
+
include Namo::Formulary
|
|
8
|
+
def net_volume(row); gross(row) - row[:sells] * 2; end
|
|
9
|
+
private
|
|
10
|
+
def gross(row); row[:buys] + row[:sells]; end
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
describe Namo do
|
|
7
14
|
let(:sample_data) do
|
|
8
15
|
[
|
|
@@ -1028,6 +1035,36 @@ describe Namo do
|
|
|
1028
1035
|
_(proc{flows.detach(untagged)}).must_raise ArgumentError
|
|
1029
1036
|
end
|
|
1030
1037
|
end
|
|
1038
|
+
|
|
1039
|
+
describe "private support methods" do
|
|
1040
|
+
it "resolves a public formulary method through its private helper" do
|
|
1041
|
+
namo = Namo.new(flow_data).attach(SupportedFlow)
|
|
1042
|
+
_(namo.values(:net_volume)).must_equal [20, -40, 0]
|
|
1043
|
+
_(namo.derived_dimensions).must_equal [:net_volume]
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
it "resolves an anonymous formulary's public method through its private helper (carrier-collected, host-resolved)" do
|
|
1047
|
+
anonymous = Module.new do
|
|
1048
|
+
include Namo::Formulary
|
|
1049
|
+
def net_volume(row); gross(row) - row[:sells] * 2; end
|
|
1050
|
+
private
|
|
1051
|
+
def gross(row); row[:buys] + row[:sells]; end
|
|
1052
|
+
end
|
|
1053
|
+
namo = Namo.new(flow_data).attach(anonymous)
|
|
1054
|
+
_(namo.values(:net_volume)).must_equal [20, -40, 0]
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
it "lets a later-attached formulary's same-named private helper shadow the earlier one (single shared host)" do
|
|
1058
|
+
shadower = Module.new do
|
|
1059
|
+
include Namo::Formulary
|
|
1060
|
+
def momentum(row); 0; end
|
|
1061
|
+
private
|
|
1062
|
+
def gross(row); 0; end
|
|
1063
|
+
end
|
|
1064
|
+
namo = Namo.new(flow_data).attach(SupportedFlow).attach(shadower)
|
|
1065
|
+
_(namo.values(:net_volume)).must_equal [-80, -240, -150]
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1031
1068
|
end
|
|
1032
1069
|
|
|
1033
1070
|
describe "formularies via class include" do
|
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.26.
|
|
4
|
+
version: 0.26.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
98
|
- !ruby/object:Gem::Version
|
|
99
99
|
version: '0'
|
|
100
100
|
requirements: []
|
|
101
|
-
rubygems_version: 4.0.
|
|
101
|
+
rubygems_version: 4.0.15
|
|
102
102
|
specification_version: 4
|
|
103
103
|
summary: Named dimensional data for Ruby.
|
|
104
104
|
test_files: []
|