Zum Inhalt

Jade / JDL — Designentscheidungen

Status: Informative Design-Notizen / historischer Kontext
Stand: aktualisierte Konsolidierungsfassung
Geltungsbereich: Architektur- und Sprachentscheidungen, die noch nicht vollständig in normative Specs überführt wurden.

Dieses Dokument ist nicht normativ. Es beschreibt Designentscheidungen, Arbeitskonsens und technische Richtung. Bei Konflikten gelten die konsolidierten Spezifikationen, insbesondere Typsystem, Refinements, TypeFns, Tags/Phantoms, Runtime/JME, Plugin-System, IR, CompilerDb und VM-Spezifikationen.

0. Konsolidierungsstand

Die alte Fassung dieses Dokuments enthielt mehrere historisch gewachsene Skizzen. Diese Fassung normalisiert die Beispiele auf den aktuellen JDL-Kurs:

  • def statt fn.
  • Funktionstypen immer mit runden Klammern: (T) -> U, () -> U.
  • X -> Y bleibt Cast-/Flow-Schreibweise und ist kein Funktionstyp.
  • Refinements werden user-facing über :> und Refinement-TypeFns geschrieben.
  • Direkte Meta-Record-Literale wie <{ ... }> gelten hier nur als erklärende interne Form, nicht als empfohlene Oberflächensyntax.
  • Rc ersetzt die frühere Bezeichnung Ref für refcounted Speicher.
  • Array[T] ersetzt alte Vec[T]-Namen.
  • Keine rohen Pointer in JDL; FFI nutzt explizite, gekapselte Grenztypen.
  • Blueprints, Newtypes, &, Type-Level-Normalisierung und Row-Polymorphismus werden als zentrale Typalgebra-Werkzeuge berücksichtigt.
  • tag, phantom, reified, dyn und zero-field Newtypes bleiben ontologisch getrennt.

Merksatz:

Struktur, Identität, Verhalten, Policy und Runtime-Bindung bleiben getrennte Ebenen.

1. Operator .., Intervalle und reaktive Driver

Entscheidung

.. ist ein normaler protokollgebundener Operator. Er ist kein Sonderfall im Compiler.

Der Operator beschreibt eine Verbindung zwischen zwei Werten. Welche Bedeutung diese Verbindung hat, entscheidet das Protocol des Typs:

  • Steppable[T] erzeugt diskrete Bereiche: Range[T].
  • Interpolatable[T] erzeugt kontinuierliche Übergänge: Interval[T].

Interval[T] ist der bevorzugte Name für kontinuierliche Übergänge. Der frühere Name Span[T] bleibt historischer Kontext, ist aber semantisch schlechter, weil „Span“ in Systemprogrammierung bereits zu viele Bedeutungen mitbringt. Ja, natürlich war ein weiterer überladener Begriff genau das, was die Welt gebraucht hätte.

JDL-Skizze

protocol Interpolatable[T] {
    def interpolate(self, other: T, t: f64) -> T
}

protocol Spannable[T] {
    def span(self, other: T) -> Interval[T]
} :> Operator("..")

provide[T] Spannable[T] for T
where T: Interpolatable[T] + Copyable
{
    def span(self, other: T) -> Interval[T] =
        Interval { from: self, to: other }
}

Verwendung:

val gradient = Color.green..Color.red

val current = gradient.at(0.5)

Driver-Prinzip

Die Pipeline bleibt pure. Effekte kommen von außen durch Driver.

val gradient = Color.green..Color.red

gradient.driveWith(Clock.tick(16ms))
gradient.driveWith(scrollPosition())
gradient.driveWith(audioLevel())

Das ergibt reaktive Systeme als emergente Eigenschaft, nicht als eigenes Sprachfeature. Genau so sollte Sugar riechen: nützlich, aber nicht nach Compiler-Sondermüll.


2. Parser-Kombinatoren

Entscheidung

Jade verwendet für den Bootstrap-Compiler einen Parser-Kombinator-Ansatz, keinen Parser-Generator.

Kernpunkte:

  • Kombinatoren: once, many, optional, sequence, choice.
  • D-Implementierung bevorzugt @nogc, nothrow, pure, wo sinnvoll.
  • struct statt class, um GC-Overhead und Vererbungsbäume zu vermeiden.
  • ref-Parameter statt roher Pointer in D-internen Hot-Paths, soweit möglich.
  • D-Slices dienen als Zero-Copy-Fenster in bestehende Source-Buffer.
  • AST-Knoten sollen als Sum Types/Tagged Unions modelliert werden, nicht als Klassenhierarchie.
  • Fehler laufen über strukturierte Diagnostic-Werte.

Ergebnis-Typ im Bootstrap-Compiler

Im Bootstrap-Compiler ist JadeResult[T] der einheitliche Ergebnistyp:

Ok(T)            Ergebnis vorhanden
Err(Diagnostic) Fehler mit Diagnose
Empty            korrekt ausgeführt, aber kein Wert vorhanden

Empty ersetzt im Bootstrap-Compiler viele Option[T]-Anwendungen. Das ist eine Compiler-Implementierungsentscheidung, keine allgemeine JDL-Stdlib-Regel.


3. Diagnostics und Fehlermeldungen

Entscheidung

Diagnostik ist ein eigener First-Class-Entwurfsbereich, kein nachträgliches println mit Größenwahn.

struct Diagnostic {
    Phase      phase;
    Severity   severity;
    SourceSpan span;
    string     message;
    Hint[]     hints;
    Note[]     notes;
}

struct Hint {
    SourceSpan span;
    string     message;
}

struct Note {
    string message;
}

Kernregeln:

  • Jede Compilerphase kann eigene Diagnosen erzeugen.
  • Hints dürfen auf andere Source-Spans zeigen als der primäre Fehler.
  • Notes erklären Zusammenhänge ohne eigenen Source-Span.
  • Diagnosen müssen die TypeEngine-/Closure-/Context-Informationen nutzen können.
  • Label- und Type-Algebra-Fehler sollen Proof-Traces bzw. Begründungsketten liefern.

Beispielhafte Diagnoseidee:

Fehler: `UserIndex` kann `Send` nicht erfüllen.

Grund:
  Feld `cache` trägt `Share(Local)`.

Benötigt durch:
  spawn(...) verlangt `Share(Send)` für alle Captures.

Hinweis:
  Verwende einen `SyncCache`, einen Actor, oder bewege den Wert nicht über die Task-Grenze.

Das ist die Sorte Fehler, bei der der Compiler nicht nur „nein“ sagt, sondern wenigstens erklärt, warum er wieder einmal recht hat.


4. Memory Policies und JME-Verhalten

Entscheidung

Rc ersetzt die alte Bezeichnung Ref.

Ref klang zu sehr nach Referenz. Rc beschreibt die tatsächliche Speicherstrategie: refcounted ownership über die Jade Memory Engine.

Primitive JME-Verhaltensweisen

Policy Semantik Typischer Einsatz
Value inline, kein Handle, kein JME-Objekt kleine primitive/Plain-Data-Typen
Rc refcounted Heap-Wert str, Array[T], dynamische Strukturen
Weak nicht-besitzender Verweis, upgrade() -> Option[T] Parent-Links, Zyklusvermeidung
Arena[A] Bump-Allokation, Bulk-Reset Request-/Frame-/Analyse-Daten
Pool[P] Freelist/Slab-Wiederverwendung Verbindungen, Tasks, teure Objekte

Alles andere ist Komposition aus Typstruktur, Refinements, Labels und JME-Invarianten.

type UserStats: struct {
    totalUsers:  i64
    activeUsers: i64
    avgAge:      f64
} :> Memory(Value)

type User: struct {
    name:  str
    email: str
    age:   i32
} :> Memory(Rc)

Die explizite Policy ist optional, wenn die TypeEngine sie ableiten kann. Explizit wird sie vor allem dann, wenn der Entwickler eine bestimmte Speicherstrategie erzwingen oder dokumentieren will.


5. Arenen und with

Entscheidung

Arenen brauchen kein eigenes ArenaScope-Protocol auf JDL-Oberfläche. with ist ein Sprachkonstrukt für deterministisches Scoping.

Arena-Typen werden über Refinements an die JME gebunden:

type RequestArena: struct {
    capacity: usize
    growable: bool
} :> Memory(Arena[RequestArena])

Konzeptionell:

open()   -> Speicherblock reservieren, cursor = base, Arena auf Scope-Stack pushen
alloc()  -> cursor += size
reset()  -> cursor = base
close()  -> reset + free + vom Scope-Stack entfernen

Verwendung:

def handleRequest(storage: Storage, req: HttpRequest) -> Result[HttpResponse, AppError] {
    with storage.arena[RequestArena] {
        val parsed =? parseRequest(req)
        val body   = renderResponse(parsed)
        Ok(HttpResponse { body })
    }
}

Grundregeln:

  • Werte aus einer Arena dürfen die Arena nicht als Referenz verlassen.
  • Rückgabe aus einem Arena-Scope kopiert in die Zielregion, wenn nötig und zulässig.
  • Nicht-kopierbare Werte können Arena-Grenzen nicht via Return verlassen.
  • Ressourcen mit Custom-Drop gehören normalerweise nicht in kurzlebige Datenarenen, sondern in eigene with-Scopes.

6. Slices und Views

Entscheidung

Slices sind Views, solange der Ursprung gültig bleibt. Über Scope-Grenzen hinweg wird kopiert, sofern das Typsystem die Kopie erlaubt.

val arr   = [1, 2, 3, 4, 5]
val slice = arr[1..3]
process(slice)

Über Scope-Grenze:

def firstHalf(arr: [i32]) -> [i32] {
    arr[0..arr.len / 2]
}

Wenn der Slice nicht sicher als View zurückgegeben werden kann, materialisiert der Compiler eine Kopie in der Zielregion. Wer das vermeiden will, gibt einen Generator[T] zurück.

def firstHalfGen(arr: [i32]) -> Generator[i32] =
    arr[0..arr.len / 2].gen()

Das ist ergonomisch, nicht maximal low-level. Wer jede Kopie selbst verwalten will, darf gerne mit FFI und Messinghelm in den Keller steigen.


7. Strings

Entscheidung

Strings sind UTF-8 und immutable by default.

Kernpunkte:

  • str ist JME-verwaltet, typischerweise Memory(Rc).
  • Mutation erzeugt eine neue Repräsentation oder nutzt explizite Builder.
  • Keine rohen Pointer in JDL; FFI-Grenzen nutzen explizite FFI-Typen und Cast-/Wire-Regeln.

Interning

Compile-Zeit:
    String-Literale -> Interning-Table im Artefakt

Runtime:
    kurze Strings können optional interned werden
    lange Strings werden nicht automatisch interned

StringBuilder

protocol Buildable {
    type Item
    type Output

    def append(self, value: Self.Item) -> Self
    def build(self) -> Self.Output
}

val s = str.builder()
    .append("Hello, ")
    .append(name)
    .append("!")
    .build()

Associated Types vermeiden hier generischen Lärm. Ja, manchmal ist Typentheorie tatsächlich ergonomisch. Widerlich.


8. Collections: Array-Familie

Dynamisches Array

Array[T] ist die dynamisch wachsende Heap-Collection.

type Array[T]: struct {
    len: usize
    cap: usize
} :> Memory(Rc)

Eigenschaften:

  • Wachstum kann Reallokation benötigen.
  • JME kann Speicher intern bewegen; Handles bleiben stabil.
  • withCapacity ist ein Optimierungskonstruktor, keine semantische Pflicht.

Statische Buffer

typefn StackBuffer[T, N]
where N: Literal[usize]
=
    struct {
        data:   [T; N]
        filled: usize
    } :> Memory(Value)

Verwendung:

type Rgb  = StackBuffer[u8, 3]
type Rgba = StackBuffer[u8, 4]
type Mat4 = StackBuffer[f32, 16]

Notation

[T]         Slice / View, kein Ownership
[T; N]      statisches Array mit Compile-Time-Größe
Array[T]    dynamische, heap-verwaltete Collection

9. Map[K, V] als typalgebraische Collection-Familie

Alte Idee, modern gefasst

Die alte Entscheidung war: Map[K, V] ist kein einzelner primitiver Typ, sondern ein Default auf eine konkrete Implementierung wie HashMap[K, V].

Die aktualisierte Fassung geht weiter:

Map ist eine materialisierbare Collection-Familie.
Ihre konkrete Form kann durch TypeFns, Blueprints, Newtypes, Refinements und Type-Algebra beschrieben werden.

Damit ist Map[K, V] nicht nur „HashMap mit freundlichem Namen“, sondern Teil eines konsistenten Modells:

  • Protocol beschreibt Verhalten.
  • TypeFn wählt Default-Repräsentation.
  • Blueprint beschreibt Strategie/Policy.
  • & spezialisiert Blueprint- oder Record-Shapes.
  • Newtype materialisiert eine domain-spezifische TypeId.
  • Refinements setzen Memory-/Wire-/Share-/Drop-Policies.
  • Row-Polymorphismus erlaubt APIs, die nur benötigte Fähigkeiten verlangen.

Verhalten: MapLike

Collection[K, V] war zu allgemein. Für key/value-Strukturen ist MapLike präziser.

protocol MapLike {
    type Key
    type Value

    def get(self, key: Self.Key) -> Option[Self.Value]
    def contains(self, key: Self.Key) -> bool
    def len(self) -> usize
}

Mutable und persistente Maps werden getrennt:

protocol MutableMap: MapLike {
    def set(self mut, key: Self.Key, value: Self.Value) -> ()
    def delete(self mut, key: Self.Key) -> bool
}

protocol PersistentMap: MapLike {
    def set(self, key: Self.Key, value: Self.Value) -> Self
    def delete(self, key: Self.Key) -> Self
}

Das vermeidet die alte API-Unschärfe, bei der set gleichzeitig wie Mutation und persistent update roch. Diese Mischung ist nicht Flexibilität, sondern Semantik mit Tarnkappe.

Konkrete Implementierungen

type HashMap[K, V]: struct {
    buckets: Array[Option[(K, V)]]
    len:     usize
} :> Memory(Rc)

Zusätzliche Constraints werden durch die jeweilige Implementierung verlangt:

HashMap[K, V]      verlangt K: Hashable + Equatable
TreeMap[K, V]      verlangt K: Comparable
SmallMap[K, V, N]  verlangt K: Equatable und N: Literal[usize]

Konzeptionell:

type TreeMap[K, V]: struct {
    root: Option[Rc[TreeNode[K, V]]]
    len:  usize
} :> Memory(Rc)
type SmallMap[K, V, N]: struct {
    entries: StackBuffer[(K, V), N]
    len:     usize
} :> Memory(Value)

Wichtig: SmallMap darf Memory(Value) nur tragen, wenn K, V und N diese Repräsentation zulassen. Das muss die TypeEngine ableiten oder prüfen. Wunschdenken ist kein Layout-Algorithmus.

Default: Map[K, V]

typefn Map[K, V]
where K: Hashable + Equatable
=
    HashMap[K, V]

Regel:

Map[K, V] ist ein bequemer Default, aber kein stabiles ABI-/Wire-Versprechen.

Öffentliche APIs, persistente Speicherung und Wire-Grenzen sollen entweder eine konkrete Implementierung oder ein explizites Blueprint/Newtype-Modell verwenden.

Blueprint: MapSpec

Eine Map-Strategie ist deklarativ beschreibbar. Dafür eignet sich ein Blueprint.

type MapStrategy: enum =
    | Hash
    | Tree
    | Inline

type OrderingPolicy: enum =
    | Unordered
    | InsertionOrdered
    | Sorted

type Mutability: enum =
    | Mutable
    | Persistent

 type MapSpec[K, V]: blueprint {
    strategy:   MapStrategy    = .Hash
    ordering:   OrderingPolicy = .Unordered
    mutability: Mutability     = .Mutable
    storage:    StoragePolicy  = .Rc
}

Spezialisierung über &:

type TreeMapSpec[K, V] =
    TreeMapSpec(
        MapSpec[K, V] & {
            strategy: .Tree
            ordering: .Sorted
        }
    )

type SmallMapSpec[K, V, N] =
    SmallMapSpec(
        MapSpec[K, V] & {
            strategy: .Inline
            storage:  .Value
        }
    )

& erweitert hier nicht beliebig. Es spezialisiert bestehende Felder. Rechts gewinnt bei kompatiblen Feldkollisionen.

Domain-Newtypes über Maps

type UserIndex = UserIndex(Map[UserId, User])

Bedeutung:

UserIndex hat die Repräsentation einer Map[UserId, User], aber eine eigene TypeId.

Das ist wichtig für:

  • Coherence,
  • domänenspezifische Methoden,
  • API-Grenzen,
  • Wire-/Storage-Policies,
  • semantische Trennung gleich strukturierter Maps.

Beispiel:

provide UserIndex {
    def findById(self, id: UserId) -> Option[User] =
        self.get(id)
}

Ein Alias wäre dagegen nur ein Name:

type UserIndexAlias = Map[UserId, User]

Der Alias ist dieselbe Map. Der Newtype ist eine neue Domäne. Kleine Syntax, große Konsequenz. So funktioniert gutes Sprachdesign gelegentlich, sehr zur Irritation aller Beteiligten.


10. Bäume und Hierarchien

Entscheidung

Parent/Child-Strukturen nutzen Rc und Weak, um Ownership und Back-References sauber zu trennen.

type Node: struct {
    value:    i32
    parent:   Weak[Node]
    children: Array[Rc[Node]]
}

Regeln:

  • Parent besitzt Children über Rc.
  • Child kennt Parent über Weak.
  • Weak.upgrade() liefert Option[T].
  • Refcount-Zyklen werden dadurch vermieden.

Alternative Designs mit Actor-State, Arena-Bäumen oder Index-basierten Graphen bleiben möglich. Die Standardempfehlung ist kein Dogma, nur weniger dumm als zyklische starke Referenzen.


11. Tags, zero-field Newtypes und Witness Tokens

Entscheidung

JDL trennt drei Konzepte:

tag X
    Compile-Time-Bedeutung, kein Wert, kein Layout.

type X = X({})
    runtime-materialisierbarer Wert ohne Nutzdaten.

type T[phantom X]
    Werttyp trägt eine compile-time Bedeutung ohne Runtime-Feld.

Tags

tag Verified
tag Registered

Tags sind Bedeutungen. Sie können nicht konstruiert oder als Wert übergeben werden.

Phantom-Zustand

type User[phantom State]: struct {
    id:    UserId
    email: Email
}

Zero-field Newtype

type AdminToken = AdminToken({})

AdminToken ist ein echter Werttyp mit eigener TypeId, aber ohne Nutzdaten. Das ist nützlich für:

  • Capability Tokens,
  • Witness Values,
  • Proof Tokens,
  • API-Gates,
  • State Tokens mit realer Übergabe.

Beispiel:

def deleteUser(token: AdminToken, id: UserId) -> Result[(), DeleteError]

Regel:

Nur Bedeutung? tag.
Bedeutung an Werttyp binden? phantom.
Konkreter Beweis-/Berechtigungswert nötig? zero-field Newtype.

12. Composability als Kernprinzip

Entscheidung

Jade implementiert wenige primitive Verhaltensweisen in Ring 0/1. JDL komponiert daraus höhere Semantik.

Beispiel:

type SensitiveData: struct {
    payload: [u8]
} :> Memory(Arena[SecureArena])
  :> Share(Local)
  :> Derive([!Copyable, !Inspectable])
  :> Create(Factory[SecureVault])
  :> Drop(Custom)

Interpretation:

Memory(Arena[SecureArena])
    physisch kontrollierte Speicherregion

Share(Local)
    keine Task-Grenze

!Copyable
    keine implizite Kopie

!Inspectable
    keine automatische Debug-/Inspect-Ausgabe

Drop(Custom)
    explizites Zeroing oder eigene Freigabelogik

Der Benutzer deklariert Absicht. Die TypeEngine leitet Garantien ab. Labels, TruthProfile und Verifier entscheiden, was tatsächlich erlaubt ist.


13. Effect-orientierte Stdlib-Bausteine

Status: Informativ. Stdlib-Planung, nicht normativ.

Schedule[E]

Ein Schedule[E] beschreibt Retry-/Backoff-Entscheidungen.

type Schedule[E] = (u32, E) -> Option[Duration]
def exponentialBackoff[E](base: Duration) -> Schedule[E] =
    (attempt, _) => Some(base * 2.pow(attempt))

def maxRetries[E](n: u32) -> Schedule[E] =
    (attempt, _) => if attempt < n then Some(0ms) else None

def both[E](a: Schedule[E], b: Schedule[E]) -> Schedule[E] =
    (attempt, error) => match (a(attempt, error), b(attempt, error)) {
        | (Some(d1), Some(d2)) => Some(d1.max(d2))
        | _                    => None
    }

Keine VM-Magie. Nur Funktionstypen und Composition.

RequestResolver

Ein RequestResolver bündelt gleichartige Requests automatisch, um N+1-Probleme zu vermeiden.

protocol Batchable {
    type Request
    type Response

    def batch(requests: [Self.Request]) -> [Self.Response]
}

Die Scheduler-/Effect-Engine kann Tasks parken, gleichartige Requests sammeln und danach gemeinsam fortsetzen. Das ist Runtime- und Stdlib-Logik, kein neues Sprachkonstrukt.

Cause[E]

Cause[E] erhält parallele und sequenzielle Fehlerursachen.

type Cause[E]: enum =
    | Fail(E)
    | Interrupt
    | Sequential(Cause[E], Cause[E])
    | Parallel(Cause[E], Cause[E])

Nützlich für parallele Graphen und Effects, bei denen mehrere Fehler gleichzeitig entstehen können.

FiberRef[T]

FiberRef[T] bleibt deferred.

Begründung:

  • Ambient State ist gefährlich bequem.
  • Viele Use Cases lassen sich über Services, Effects, Actors oder ContextDescriptoren sauberer ausdrücken.
  • Neue VM-Intrinsics für Task-local Storage sollen nur eingeführt werden, wenn ein echter Use Case bleibt.

Mit anderen Worten: erst beweisen, dann einbauen. Revolutionär.


14. Scripting Host via .jdm und Hot-Reload

Status: Informativ. Noch nicht spec-reif.

Entscheidung

Jade braucht keinen separaten Scripting-Layer wie Lua. Die VM selbst ist der Scripting Host. .jdm-Module sind kompiliert, typgeprüft und ladbar.

Grundprinzip:

  • Host-Applikation exportiert API als JDL-/JDM-Kontrakt.
  • Script-Code importiert diesen Kontrakt.
  • Compiler prüft Scripts statisch.
  • VM lädt kompiliertes .jdm zur Laufzeit.
  • State kann über Handles erhalten bleiben.

Hot-Reload-Idee

jade build --module patrol.jdl -> patrol.jdm
Engine erkennt neue patrol.jdm
-> alte PatrolTask kooperativ stoppen
-> neue PatrolTask starten
-> bestehender Shared[ScriptState]-Handle wird übergeben

JDL-Sicht:

type ScriptState: struct {
    currentTarget: Option[Vec3]
    patrolIndex:   usize
} :> Share(Sync)

Der State überlebt Reload, weil die Engine den Handle hält. Die Task kann sterben, ohne den State zu vernichten.

Offene Fragen

  • Wie granular ist Hot-Reload? Funktion, Modul, Plugin?
  • Wie werden Script-APIs versioniert?
  • Wie erzwingen Package-/Plugin-Constitutions Sandboxing?
  • Wie sieht Fehlerreporting im laufenden Host aus?
  • Wie werden migrationspflichtige State-Layouts behandelt?

15. Offene Punkte

Diese Punkte gehören nicht mehr als lose Ideensammlung ans Ende, sondern als konkrete TODO-Liste für spätere Konsolidierung:

  1. Collections normativ konsolidieren
  2. MapLike, MutableMap, PersistentMap
  3. Array[T], [T], [T; N]
  4. Wire-/ABI-Stabilität konkreter Collections

  5. MapSpec / Collection Blueprints prüfen

  6. Wann lohnt Blueprint gegenüber normaler TypeFn?
  7. Welche Felder sind Strategie, welche sind Policy?
  8. Welche Engine materialisiert daraus konkrete Typen?

  9. Function Captures und Blueprint-lokale Funktionen

  10. Inline-Funktionen dürfen nur Parameter, Blueprint-Felder, FQN/importierte Symbole und Context-Services referenzieren.
  11. Externe Lexical Captures bleiben verboten oder müssen explizit materialisiert werden.

  12. Variance finalisieren

  13. immutable Container kovariant
  14. mutable Container invariant
  15. Funktionsparameter kontravariant
  16. Funktionsrückgaben kovariant
  17. Effect[R, E, D]: D über Row-Satisfaction, nicht naive Subtyping-Variance

  18. FFI-Boundary für Buffer/Slices

  19. keine rohen Pointer in JDL
  20. explizite FFI-Grenztypen
  21. Arena-/Slice-Escape-Regeln

  22. =? Lowering in VM/IR

  23. Desugaring auf match/early return
  24. Cleanup-Semantik bei Disposable und with
  25. Diagnosequalität

16. Kurzfassung

Jade trägt primitive Wahrheiten.
JDL komponiert daraus höhere Semantik.

Die wichtigsten aktualisierten Entscheidungen:

  • .. bleibt normaler Operator über Protocols.
  • Interval[T] ist die bevorzugte kontinuierliche Form.
  • Parser-Kombinatoren bleiben Bootstrap-Strategie.
  • Diagnostics sind strukturiert und proof-/trace-fähig.
  • Rc ersetzt Ref.
  • Arenen werden über with und Memory(Arena[A]) geführt.
  • Slices sind Views, Scope-Escape erzeugt Kopie oder Fehler.
  • Strings sind UTF-8 und immutable.
  • Collections werden als typalgebraische Familien verstanden.
  • Map[K,V] ist Default, nicht ABI-/Wire-Vertrag.
  • Blueprints und & ermöglichen Collection-Spezialisierung.
  • Newtypes materialisieren Domänen über bestehenden Carriern.
  • Tags, Phantoms und zero-field Newtypes bleiben getrennte Werkzeuge.
  • Effect-artige Stdlib-Features entstehen aus normalen JDL-Bausteinen.
  • Scripting via .jdm bleibt ein starkes, aber noch zu spezifizierendes Ziel.

Guter Tag für Jade. Schrecklicher Tag für halbherzige Typmodelle.