Zum Inhalt

Dyn[P] — Dynamische Protocol-Werte

Status: Normativ (v0.1)
Geltungsbereich: Typsystem, VM-Dispatch, Runtime-Repräsentation
Abhängigkeiten: 02-typsystem.md, dispatch-modes.md, 11-jadevalue-und-register-layout.md, 10-runtime-handles-und-memory-safety.md
Autor: Elias


1. Zweck

Dyn[P] ist JDL's Mechanismus für dynamischen Protocol-Dispatch. Er löscht den konkreten Typ eines Wertes und ersetzt ihn durch ein Protocol-Interface. Der konkrete Typ ist zur Compile-Zeit nicht bekannt, das Protocol schon.

Dyn[P] ist semantisch äquivalent zu Rust's dyn Trait: immer Protocol-gebunden, niemals ungebunden. Es gibt in JDL keinen vollständig gelöschten "ich weiß nichts"-Typ. Jeder dynamische Wert hat mindestens eine Protocol-Garantie.


2. Dyn[P] ist kein TypeKind

TypeKinds (Primitive, Struct, Enum, Union, Function, Tuple, Blueprint, Flags) beschreiben welche Art von Typ etwas ist. Dyn[P] beschreibt dass der konkrete Typ gelöscht wurde.

Ein Dyn[Serializable] kann ein Struct sein, ein Enum, ein Blueprint — der TypeKind dahinter ist unbekannt. Dyn[P] ist orthogonal zu TypeKinds, nicht parallel. Er ist ein parametrischer Typ-Konstruktor der jeden TypeKind hinter einem Protocol-Interface wrappen kann.

Konsequenz: Dyn steht nicht in der TypeKind-Liste. Die VM kennt Dyn[P] als eigene Wertekategorie mit spezieller Repräsentation (siehe §5), nicht als TypeKind.


3. Syntax

// Deklaration eines dynamischen Protocol-Werts
val item: Dyn[Printable] = someValue

// In Funktionssignaturen
def process(items: [Dyn[Serializable]]) -> Result[(), ProcessError]

// Mehrere Protocol-Bounds (Intersection)
val entry: Dyn[Serializable + Printable] = someValue

P muss ein Protocol sein. Primitive Typen, Structs und andere nicht-Protocol Typausdrücke sind als Bound nicht zulässig.


4. Semantik

4.1 Protocol-Dispatch

Ein Dyn[P]-Wert garantiert, dass der verborgene Typ das Protocol P implementiert. Methoden von P können direkt aufgerufen werden:

def printAll(items: [Dyn[Printable]]) -> () {
    for item in items {
        item.print()  // dynamischer Dispatch — P ist bekannt, T nicht
    }
}

4.2 Typ-Recovery via Match

Der konkrete Typ kann zur Laufzeit über Pattern Matching wiederhergestellt werden:

def handleBlueprint(spec: Dyn[Blueprint]) -> Result[(), DispatchError] {
    match spec {
        | s: CallGraph     => run(s, effectRuntime)
        | s: ActorSpec     => run(s, actorSupervisor)
        | s: SchedulerSpec => run(s, scheduler)
        | _ => Err(DispatchError.UnknownBlueprintType { received: spec.typeId })
    }
}

Innerhalb eines Match-Arms ist der Wert vollständig statisch typisiert. Das Match ist die Grenze zwischen dynamischer und statischer Welt.

4.3 Typ-Recovery via TryCast

Außerhalb von Match kann der konkrete Typ über einen TryCast wiederhergestellt werden:

val concrete =? spec ->? CallGraph
// ab hier: concrete ist CallGraph, vollständig statisch typisiert

Der TryCast gibt Result[T, CastError] zurück. =? propagiert den Fehler.

4.4 Erzeugung von Dyn[P]-Werten

Ein konkreter Wert wird zu einem Dyn[P]-Wert, wenn er an eine Stelle fließt die Dyn[P] erwartet — vorausgesetzt sein Typ implementiert P:

val user = User { name: "Alice", email: "alice@example.com" }

// User implementiert Printable → implizite Konversion zu Dyn[Printable]
val printable: Dyn[Printable] = user

// User implementiert Printable nicht? → Compile-Fehler

Die Konversion von T zu Dyn[P] ist implizit wenn T das Protocol P implementiert. Dies ist der einzige Punkt an dem der Compiler den konkreten Typ löscht und einen DynValue erzeugt.


5. Runtime-Repräsentation

5.1 DynValue

Ein Dyn[P]-Wert wird zur Laufzeit als DynValue repräsentiert — ein JadeValue-Wrapper mit persistenter Typidentität:

// D-Implementierung (normativ für Struktur, nicht für Syntax)
struct DynValue {
    JadeValue payload;  // der verborgene Wert — Immediate oder Handle
    TypeId    typeId;   // persistente Runtime-Typidentität
}

DynValue ist größer als ein einzelner JadeValue-Slot (> 8 Byte) und wird vom JME auf dem Heap alloziert. An Funktionsgrenzen reist ein Dyn[P]-Wert als normales JadeValue.handle — ein HandleId der auf den heap-allozierten DynValue zeigt.

5.2 Heap-Allokation

Jeder Dyn[P]-Wert kostet eine JME-Allokation — auch wenn der verborgene Payload ein i32 ist. Das ist für die legitimen Anwendungsfälle von Dyn[P] (Plugin-Grenzen, Service-Registries, heterogene Collections, Netzwerk-Deserialisierung) akzeptabel, da diese typischerweise bei Initialisierung stattfinden, nicht in engen Loops.

5.3 Binding-Auflösung

Die interne Repräsentation der Protocol-Binding-Auflösung — ob Lookup-on-use, captured Witness, Protocol Table oder Strategy Slot pro (T, P) — ist VM-intern und nicht Teil des Sprachvertrags. Normativ ist nur: korrekte Binding-Auflösung zum Aufrufzeitpunkt muss garantiert sein.

5.4 Abgrenzung zu JadeValue

JadeValue ist ein ungetaggter Transportcontainer (Invariante: kein Laufzeit-Tag). Dyn[P] erfordert zwingend Runtime-Typinformation — der fundamentale Widerspruch zu JadeValue's No-Tag-Invariante. Dyn[P]-Werte sind daher eine eigene VM-interne Kategorie, die semantisch oberhalb von JadeValue liegt.

Ebene Beschreibung Tag?
Statischer / generischer Wert VM-interner Registerwert, kontextgetypt nein
JadeValue Grenzformat für Call/Return/Intrinsic/FFI nein (Bytecode-Kontext)
DynValue (Dyn[P]) Existential Container mit Runtime-Typinfo ja (erforderlich)

6. Anwendungsfälle

6.1 Heterogene Collections

val handlers: [Dyn[Handler]] = [
    userHandler,      // UserHandler
    orderHandler,     // OrderHandler
    paymentHandler    // PaymentHandler
]

for handler in handlers {
    handler.handle(request)  // dynamischer Dispatch auf Handler-Protocol
}

6.2 Plugin-Grenzen

def loadPlugin(path: str) -> Result[Dyn[EngineFactory], PluginError] {
    val plugin =? Plugin.load(path)
    plugin.resolve("engineFactory") ->? EngineFactory
}

Der Host kennt das EngineFactory-Protocol, nicht den konkreten Plugin-Typ.

6.3 Netzwerk-Deserialisierung

def receiveBlueprint(stream: ByteStream) -> Result[Dyn[Blueprint], DeserializeError] {
    val typeTag =? stream.readTypeTag()
    val payload =? stream.readPayload()
    Deserializable.deserialize(typeTag, payload)
}

Der deserialisierte Wert ist Dyn[Blueprint] — ein Blueprint dessen konkreter Typ erst zur Laufzeit via Match bestimmt wird.

6.4 Service-Registries

type ServiceRegistry : struct {
    services: Map[str, Dyn[Service]]
}

def resolve[S: Service](registry: ServiceRegistry, name: str) -> Result[S, ServiceError] {
    val entry =? registry.services.get(name)
    entry ->? S
}

7. Nicht-Anwendungsfälle

Dyn[P] ist kein Ersatz für Generics. Wenn der konkrete Typ zur Compile-Zeit bekannt sein kann, ist statischer oder generischer Dispatch die richtige Wahl:

// Falsch — Dyn[P] ohne Grund
def addPair(a: Dyn[Addable], b: Dyn[Addable]) -> Dyn[Addable]

// Richtig — generisch, statisch auflösbar
def addPair[T: Addable](a: T, b: T) -> T

Dyn[P] ist ein gezieltes Werkzeug für Grenzen an denen der konkrete Typ nicht bekannt sein kann, nicht ein allgemeiner Abstraktionsmechanismus.


8. Normative Regeln

DYN-1:  Dyn[P] ist ein parametrischer Typ-Konstruktor, kein TypeKind.
        Er ist orthogonal zu TypeKinds und kann jeden TypeKind wrappen.
DYN-2:  P muss ein Protocol sein. Dyn ohne Protocol-Bound existiert nicht.
DYN-3:  Methodenaufrufe auf Dyn[P] dispatchen dynamisch über das
        Protocol P. Der konkrete Typ wird zur Laufzeit aufgelöst.
DYN-4:  Typ-Recovery erfolgt über Pattern Matching oder TryCast (->?).
        Innerhalb eines Match-Arms ist der Wert vollständig statisch
        typisiert.
DYN-5:  Die Konversion von T zu Dyn[P] ist implizit wenn T das
        Protocol P implementiert. Dies ist der einzige Punkt an dem
        der Compiler den konkreten Typ löscht.
DYN-6:  Ein Dyn[P]-Wert wird zur Laufzeit als DynValue repräsentiert —
        ein JadeValue-Wrapper mit persistenter TypeId. DynValue wird
        vom JME auf dem Heap alloziert.
DYN-7:  Dyn[P] ist kein JadeValue. DynValue liegt semantisch oberhalb
        von JadeValue und trägt zwingend Runtime-Typinformation.
DYN-8:  Dyn[P] ist kein Ersatz für Generics. Wenn der konkrete Typ
        zur Compile-Zeit bekannt sein kann, ist statischer oder
        generischer Dispatch die richtige Wahl.

9. Merksatz

Dyn[P] heißt: ich kenne das Interface, nicht den Typ.
Ohne Protocol kein Dyn. Ohne Match kein Weg zurück.

Änderungsprotokoll

v0.1 — Initiale Fassung. Dyn[P] als parametrischer Typ-Konstruktor definiert, aus der TypeKind-Liste entfernt. Syntax, Semantik, Runtime-Repräsentation und Match-basierte Typ-Recovery spezifiziert. Abgrenzung zu JadeValue und Generics.