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:

Gemfile
gem "holatri"
shell
bundle install

Holatri benötigt Ruby ≥ 3.0 und kommt ohne Laufzeit­abhä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:

TypWertRuby
SINGLEein SkalarInteger, Float, String, Range, …
« CROWDListe (Skalare und/oder Holons)Array
TRIBEbenannte Menge (nur Holons)Hash
holon.rb
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 und Hash → TRIBE. Alles Übrige (auch Set) ist SINGLE.
  • Verschachtelte Hash/Array in 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.

capabilities.rb
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, findableabsorbable, computable, connectable

Taggable

Beliebige Tags (Symbols) an einem Holon. Strings werden zu Symbols normalisiert, Tags sind dedupliziert.

taggable.rb
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.

shapeable.rb
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.

delegable.rb
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).

inspectable.rb
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.

computable.rb
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 avg u. a. sowie die Konstanten pi(), e(). min/max/sum/avg sind variadisch.
Caching: Mit dem Tag :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.

findable.rb
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: (class per is_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 sich find wie Enumerable#find.

Absorbable · Opt-in

Liest Daten aus einer URI und lädt sie strukturiert ins Holon (Hash → TRIBE, Array → CROWD, Skalar → SINGLE).

absorbable.rb
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.

connectable.rb
# 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.

Sicherheit: Der Transport nutzt Marshal — nur mit vertrauenswürdigen Gegenstellen verwenden (Default-Bind auf 127.0.0.1).

Eine eigene Capability

  1. Modul unter Holatri::Capabilities anlegen.
  2. Mit Holatri::Capable.register(:name, self) registrieren.
  3. Optional Abhängigkeiten via Holatri::Capable.depends(self, :andere).
  4. Methoden definieren; Zustand bevorzugt über Tags abbilden.
  5. Vergabe klassenweit (capability) oder pro Instanz (gain).
timestamped.rb
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