Zum Inhalt

JDL – Qualifier, Effektsystem, Konversionen und Wire-Integration

Status: Draft
Geltungsbereich: Sprachebene (keine VM-Implementierungsdetails)


Warum Dieses Dokument Existiert

Die Kernteile 0003 beschreiben bereits die Architektur. In der Praxis bleiben aber vier Fragen offen, die beim Implementieren sofort relevant werden:

  • Wie unterscheiden sich phantom, reified und narrowing präzise?
  • Wo endet Protokollverhalten und wo beginnt Capability/Effekt?
  • Wann sind Konversionen implizit zulässig, wann verboten?
  • Wie wird Wire/Serde eingebunden, ohne Sonderlogik in die Sprache zu drücken?

Dieses Dokument schließt genau diese Lücke.


0. Priorität

Dieses Dokument konkretisiert:

  • 00-refinements.md
  • 01-grundlagen.md
  • 02-typsystem.md
  • 03-runtime.md

Bei Widerspruch gelten immer 00 bis 03.
Bei Widerspruch mit 07-cast-effekte-refinements.md gilt 07.


1. Leseführung

  • Abschnitt 2: Qualifier-Modi (rein semantische Schalter)
  • Abschnitt 3–5: Verhalten, Effekte und Konversion als zusammenhängendes System
  • Abschnitt 6: Wire/Serde als Anwendung derselben Bausteine

Leitidee: Keine Spezialmechanik, nur konsistente Komposition aus vorhandenen Sprachmitteln.


2. Qualifier-Modi

2.1 Grundsatz

Ein Qualifier-Modus verändert die Behandlung einer Bindung, nicht den Basistyp.

Er ist:

  • signaturrelevant,
  • typprüfungsrelevant,
  • inferenzrelevant,
  • codegen-relevant.

Er ist kein Refinement und kein Effekt.

2.2 phantom

type StateMachine[phantom State]

Bedeutung:

  • nur Compile-Zeit,
  • keine Runtime-Repräsentation,
  • keine Runtime-Reflection auf diesem Parameter.

2.3 reified

def describe[reified T](x: T) -> str = TypeInfo.of[T]().name

Bedeutung:

  • Typinformation wird zur Laufzeit materialisiert,
  • TypeInfo/Typecase auf Typparameter ist nur dann zulässig,
  • Reifikation ist nie implizit.

2.4 narrowing

Bedeutung:

  • keine implizite Anwendung,
  • expliziter Cast-Aufruf erforderlich,
  • Modus-Syntax ist erweiterbar (z. B. zukünftige Modi).

3. Protocol, Service, Provide

3.1 Warum Die Trennung Wichtig Ist

Viele Sprachsysteme vermischen Fähigkeiten eines Typs mit Capabilities der Außenwelt. JDL trennt das explizit:

  • protocol: Verhalten eines Typs,
  • service: Capability in Effektkontexten.

3.2 protocol

protocol Equatable {
    def equals(self, other: Self) -> bool
}

Eigenschaften:

  • kontextunabhängig,
  • für Generics/where geeignet.

3.3 service

service Db {
    def query(sql: str) -> Result[Rows, DbError]
}

Eigenschaften:

  • effektrelevant,
  • via Handler durch provide gebunden,
  • im Effect-Stil über Effect[R, E, D] deklariert,
  • im imperativen Stil als Parameter übergeben.

3.4 provide-Formen

provide hat genau drei semantisch klar getrennte Formen:

provide Equatable for User { ... }        // Protocol-Implementierung
provide User { ... }                      // Methoden am Typ
provide UserDb[PostgresHandler] { ... }   // Service-Handler-Bindung

provide Target from Source existiert nicht mehr. Konversion läuft über CastTo[T] (siehe 07-cast-effekte-refinements.md § 1).

Invarianten:

  • Protocols erscheinen nicht im Effect-Typ D.
  • Services werden nicht wie normale Protocol-Constraints behandelt.

4. Effektsystem

Überholt durch 07-cast-effekte-refinements.md §§ 2–3.

Das deps-Konstrukt auf Funktionsebene existiert nicht mehr.

Effect[R, E, D] ist ein normaler generischer Typ in der Stdlib — kein Compiler-Primitiv. Der Compiler hat kein Sonderwissen über diesen Typ. Die gesamte Semantik (Komposition, Retry, Remote, Concurrency) ist JDL-Code der auf diesem Typ aufbaut.

JDL kennt zwei Stile um Abhängigkeiten zu Services auszudrücken:

Imperativer Stil — Service als normaler Parameter, sofortige Ausführung:

def fetchUser(db: UserDb, id: UserId) -> Result[User, UserDbError] =
    db.getUserById(id)

Effect-StilEffect[R, E, D] als normaler Rückgabetyp, lazy:

def fetchUser(id: UserId) -> Effect[User, UserDbError, UserDb] =
    UserDb.getUserById(id)

Der Funktionskörper beider Varianten ist identisch. Im Effect-Stil gibt die Funktion einen Effect-Wert zurück der erst durch explizites Bereitstellen der Dependencies ausgeführt wird — das ist normale Typsemantik, kein Compiler-Sonderfall.

Runtime-Verhalten wie Retry, Remote-Ausführung und Concurrency sind Refinements auf CallGraph — ebenfalls ein normaler Stdlib-Typ:

CallGraph handleRequest(...) { ... }
    :> Retry(max: 3, backoff: Exponential)
    :> Serializable


5. Konversionssystem

Überholt durch 07-cast-effekte-refinements.md § 1.

Konversion läuft über CastTo[T]. Die alte Form provide Target from Source existiert nicht mehr.


6. Wire-/Serde-Integration

6.1 Ziel

Wire-Serialisierung soll ohne externen Generator möglich sein, aber trotzdem typisiert und vorhersagbar.

6.2 WireType

protocol WireType[T] {
    def kind() -> WireKind
    def encodeValue(x: T, w: WireWriter) -> ()
    def decodeValue(r: WireReader) -> Result[T, DecodeError]
}

6.3 Delegation Über Konversion

type UserId: struct { value: str }

provide CastTo[str]: Lossless for UserId { ... }
provide CastTo[UserId]: Try[ParseError] for str { ... }

Damit kann WireType[UserId] über WireType[str] delegieren.

6.4 Derive Für Message-Typen

Refinements auf einzelnen Feldern sind nicht zulässig — Feld-Metadaten wie Wire-Tags werden als Refinement auf dem Typ als Ganzes ausgedrückt:

type User: struct {
    id: UserId
    name: str
} :> Derive([WireMessage])
  :> WireTags({ id: 1, name: 2 })

Regeln:

  • jedes Feld genau ein tag (via WireTags),
  • Tags eindeutig,
  • Feldtyp selbst WireType oder verlustfrei auf WireType abbildbar.

6.5 Effekttrennung

  • reine Serialisierung bleibt ohne Service-Abhängigkeit,
  • IO-gebundene Nutzung übergibt den Service als Parameter oder verwendet Effect[R, E, IO].

7. Normative Invarianten

  1. Runtime-TypeInfo auf Typparameter nur mit reified.
  2. phantom bleibt runtime-inexistent.
  3. narrowing ist nie implizit.
  4. Effect[R, E, D] ist ein normaler generischer Typ in der Stdlib — kein Compiler-Primitiv. Der Compiler hat kein Sonderwissen darüber. Abhängigkeiten werden über Effect[R, E, D] im Rückgabetyp oder als explizite Parameter ausgedrückt. deps als Funktionskonstrukt existiert nicht.
  5. Im Effect-Stil gibt eine Funktion einen Effect-Wert zurück — das ist normaler Rückgabetyp, kein Compiler-Sonderfall. Im imperativen Stil werden Services als Parameter übergeben. Fehlende Parameter sind normale Compile-Fehler des Typsystems.
  6. Konversionen sind nicht transitiv.
  7. Protocol- und Service-Ebene bleiben getrennt.
  8. Wire-Delegation nutzt nur deklarierte Konversionen.

Addendum A – Formale Kurzsemantik

Das Addendum fasst die mathematischen Kernaussagen zusammen (Urteile, Effektordnung, Funktions-/Aufrufregeln, Reifikation, Konversion). Es ist als präzise Referenz gedacht, nicht als Einführungstext.