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.