Dokumentation
Holatri stellt den Datentyp Holon bereit — ein einzelnes Key/Value-Paar — und ein Capability-System, über das Holons zusätzliche Fähigkeiten erhalten.
Installation
In die Gemfile aufnehmen und installieren:
gem "holatri"
bundle install
Holatri benötigt Ruby ≥ 3.0 und kommt ohne Laufzeitabhängigkeiten aus (außer den ab Ruby 3.4 ausgelagerten Standardbibliotheken csv und rexml für Absorbable).
Das Holon
Ein Holon hat genau eine von drei Ausprägungen, abgeleitet aus seinem Wert:
| Typ | Wert | Ruby |
|---|---|---|
‹ SINGLE | ein Skalar | Integer, Float, String, Range, … |
« CROWD | Liste (Skalare und/oder Holons) | Array |
≈ TRIBE | benannte Menge (nur Holons) | Hash |
require "holatri"
# SINGLE — der Typ wird aus dem Wert abgeleitet
age = Holatri::Holon.new(:age, 42)
age.single? # => true
age.value # => 42
# CROWD — Liste aus Skalaren und/oder Holons
scores = Holatri::Holon.new(:scores, [90, 85, Holatri::Holon.new(:bonus, 5)])
scores.crowd? # => true
scores[0] # => 90 (Skalar)
scores[2] # => das Kind-Holon :bonus
scores << 70 # anhängen
# TRIBE — benannte Menge; jeder Hash-Eintrag wird zu einem Holon
person = Holatri::Holon.new(:person, {
name: "Ada",
skills: %w[ruby math], # => CROWD
pet: { kind: "cat", legs: 4 } # => TRIBE (rekursiv)
})
person.tribe? # => true
person[:name].value # => "Ada"
person[:pet][:legs].value # => 4
person.add(Holatri::Holon.new(:age, 36)) # gleicher Key: "letzter gewinnt"
person.to_h
# => { person: { name: "Ada", skills: ["ruby", "math"],
# pet: { kind: "cat", legs: 4 }, age: 36 } }
Holon bindet Enumerable ein: über ein CROWD werden die Elemente iteriert, über ein TRIBE die Kind-Holons, über ein SINGLE der einzelne Wert.
Typ-Ableitung
- Nur
Array→ CROWD undHash→ TRIBE. Alles Übrige (auchSet) ist SINGLE. - Verschachtelte
Hash/Arrayin einem CROWD werden zu anonymen Holons (key == nil). - Bei gleichem Key in einem TRIBE gilt „letzter gewinnt".
- Wegen des
capabilities:-Keywords im Konstruktor einen TRIBE-Wert mit expliziten Klammern angeben:Holatri::Holon.new(:p, { name: "Ada" }).
Capability-System
Fähigkeiten sind Module unter Holatri::Capabilities. Sie werden klassenweit (Klasse.capability(:name)) oder pro Instanz (holon.gain(:name)) vergeben; beides ist idempotent und chainable. Abhängigkeiten zwischen Capabilities werden automatisch aufgelöst.
holon = Holatri::Holon.new(:x, 1)
holon.capable_of?(:taggable) # => true
holon.capabilities # => die aktiven Capability-Module
holon.gain(:computable) # nur diese Instanz erweitern
Holatri::Holon.capability(:absorbable) # klassenweit für alle Holons
| Standard (ab Werk) | Opt-in (gezielt) |
|---|---|
taggable, shapeable, delegable, inspectable, findable | absorbable, computable, connectable |
Taggable
Beliebige Tags (Symbols) an einem Holon. Strings werden zu Symbols normalisiert, Tags sind dedupliziert.
h = Holatri::Holon.new(:task, "Doku schreiben")
h.tag(:urgent, :docs)
h.tagged?(:urgent) # => true
h.tags # => #<Set: {:urgent, :docs}>
h.untag(:docs)
tagged? ohne Argumente prüft, ob überhaupt Tags gesetzt sind; mit Argumenten, ob alle genannten vorhanden sind.
Shapeable
Wahrnehmungs-Zustand eines Holons: blurred (minimal) oder shaped (vollständig), gehalten über das Tag :shaped (daher die Abhängigkeit von Taggable). shape und blur werden an enthaltene shapeable Holons propagiert.
h.shape # scharfstellen, an Subholons weitergeben
h.shaped? # => true
h.blur # zurück auf das Minimum
Abgeleitete Klassen definieren konkretes Verhalten über die Hooks pre_shape/post_shape bzw. pre_blur/post_blur (Standard: No-op).
Delegable
Zugriff auf Kind-Holons über ihren Namen als Methode — mit Auflösung nach oben durch die Holarchie.
gesicht = Holatri::Holon.new(:gesicht, { nase: "spitz" })
gesicht.nase # => das Kind-Holon :nase
gesicht.nase.value # => "spitz"
gesicht[:nase] # => "spitz" (entspricht gesicht.nase.value)
Findet ein Holon einen Namen lokal nicht, reicht es die Anfrage an sein Superholon weiter, bis sie aufgelöst ist oder kein Superholon mehr existiert. Keys, die echten Methodennamen entsprechen, werden nicht delegiert.
Inspectable
Kompakte, lesbare Darstellung (standardmäßig aktiv). Indikatoren: ‹ SINGLE, « CROWD, ≈ TRIBE. Bei Strings werden nur die begrenzenden Anführungszeichen zu ' (escapte bleiben erhalten).
Holatri::Holon.new(:age, 42).inspect # => "‹(age:42)"
Holatri::Holon.new(:c, [1, 2, 3]).inspect # => "«(c:[1,2,3])"
Holatri::Holon.new(:person, { name: "Ada" }).inspect
# => "≈(person:[name:'Ada'])"
Computable / Formula
Ein Holon hält statt eines Wertes eine Formel; bei jedem value-Zugriff wird sie ausgewertet. Beim ersten Zugriff wird die Formel per Shunting-Yard (Dijkstra) in eine RPN-Form vorverarbeitet, danach nur noch evaluiert. Holatri::Formula ist die ab Werk computable Subklasse.
rect = Holatri::Holon.new(:rect, {})
rect.add(Holatri::Holon.new(:w, 3))
rect.add(Holatri::Holon.new(:h, 4))
rect.add(Holatri.formula(:area, "w * h"))
rect[:area] # => 12 (im Kontext der Holarchie ausgewertet)
Holatri.formula(:d, "sqrt(3 ^ 2 + 4 ^ 2)").value # => 5.0
Holatri.formula(:t, "1 + 2 * 3").inspect # => "ƒ(t:'1 + 2 * 3')"
Formelsyntax
- Zahlen: Integer und Float, auch wissenschaftlich (
1.5e3). - Operatoren:
+ - * /und^(Potenz, rechtsassoziativ), Klammern, unäres Minus. - Referenzen: Holon-Namen, aufgelöst über Delegable (Kind oder aufwärts). Eine Referenz auf eine andere Formula löst deren Berechnung aus.
- Funktionen:
abs round floor ceil sqrt cbrt exp ln log sin cos tan pow mod min max sum avgu. a. sowie die Konstantenpi(),e().min/max/sum/avgsind variadisch.
:lazy_computing wird ein einmal ermittelter Wert gecachet; recompute (bzw. clear_cache) verwirft ihn.
Findable / Filter
Durchsucht die enthaltenen Holons anhand eines Filters, dessen Kriterien selbst Holons sind. Rückgabe ist ein Array der passenden Holons.
shelf = Holatri::Holon.new(:shelf, {})
shelf.add(Holatri::Holon.new("ABC123", 10))
shelf.add(Holatri::Holon.new("XYZ789", 20))
shelf.find(Holatri.filter(key: "ABC123")) # nach Schlüssel
shelf.find(Holatri.filter(gt: 15)) # value > 15
shelf.find(Holatri.filter(gt: 5, lt: 25)) # UND (mehrere Kriterien)
shelf.find(Holatri.filter(or: [{ eq: 10 }, { eq: 20 }]))
shelf.find(Holatri.filter(value: 42), deep: true) # rekursiv
shelf.find { |h| h.value > 15 } # Block: wie Enumerable#find
- Attribut-Selektoren (Gleichheit):
key:,value:,type:,class:(classperis_a?). - Vergleichsoperatoren auf den Wert:
eq le lt gt ge ne(z. B.gt: 100), auch zusammengesetzt:value_gt: 100. - Kombinatoren:
and:,or:,xor:mit einer Liste weiterer Kriterien. Mehrere Kriterien nebeneinander sind ein UND. find(filter, deep: true)durchsucht rekursiv alle Nachfahren; mit Block verhält sichfindwieEnumerable#find.
Absorbable · Opt-in
Liest Daten aus einer URI und lädt sie strukturiert ins Holon (Hash → TRIBE, Array → CROWD, Skalar → SINGLE).
cfg = Holatri::Holon.new(:cfg, nil, capabilities: [:absorbable])
cfg.absorb("config.json") # Format aus der Endung
cfg.absorb(url, mime_type: :csv, headers: true) # explizit
cfg[:database][:host] # Zugriff wie gewohnt
Quelle: lokale Datei, file:// oder http(s). Eingebaute Formate: :json, :yaml, :xml, :csv, :tsv, :text. Weitere über Holatri::Capabilities::Absorbable.register_format(:name) { |raw, **opts| … }.
Connectable · Opt-in
Zwei Holons verständigen sich über TCP/IP — bidirektional.
# Prozess A — Quelle anbieten
source = Holatri::Holon.new(:rect, {})
source.add(Holatri::Holon.new(:w, 3))
source.add(Holatri::Holon.new(:h, 4))
source.gain(:connectable)
port = source.serve(12345) # 0 => freier Port; gibt den belegten Port zurück
# Prozess B — verbinden und delegieren
proxy = Holatri.connect("127.0.0.1", port)
proxy.type # => :tribe (an die Quelle delegiert, dort aufgelöst)
proxy[:w] # => 3
Hinweg
Der Proxy leitet seine Leser (value, type, key, []) und beliebige Methodenaufrufe an die Quelle, die sie im eigenen Kontext auflöst. Weil die übrige API (each, to_h, Prädikate) darauf aufbaut, richten sich faktisch alle Anfragen ans verbundene Holon.
Rückweg
Muss die Quelle nach oben (an ihr superholon) auflösen — etwa eine Formel-Referenz, deren Wert im Kontext des Proxys liegt —, geht die Anfrage über dieselbe Verbindung zurück an den Client und wird an dessen echtem Superholon aufgelöst. Der Proxy ist damit aus Sicht der Quelle deren Superholon.
Marshal — nur mit vertrauenswürdigen Gegenstellen verwenden (Default-Bind auf 127.0.0.1).
Eine eigene Capability
- Modul unter
Holatri::Capabilitiesanlegen. - Mit
Holatri::Capable.register(:name, self)registrieren. - Optional Abhängigkeiten via
Holatri::Capable.depends(self, :andere). - Methoden definieren; Zustand bevorzugt über Tags abbilden.
- Vergabe klassenweit (
capability) oder pro Instanz (gain).
module Holatri
module Capabilities
module Timestamped
Holatri::Capable.register(:timestamped, self)
def touch
@touched_at = Time.now
self
end
def touched_at
@touched_at
end
end
end
end
note = Holatri::Holon.new(:note, "Hallo").gain(:timestamped)
note.touch.touched_at # => aktueller Zeitstempel