Jade/JDL – Semantik-Checkliste (Spec-kohärent)¶
Status: empfohlen (non-normativ) – als Arbeitsliste zum Konsolidieren der Specs
Ziel: Semantische “Knoten” identifizieren, die in den vorhandenen Specs teilweise definiert sind, aber noch eine eindeutige, durchgehende Entscheidung brauchen.
Hinweis: Syntax-Drift ist hier bewusst außen vor. Fokus ist Bedeutung, nicht Schreibweise.
1) Meta-Records & Refinement-Merge (inkl. :>)¶
Stand in den Specs (konsistent):
- :> ist äquivalent zu <{…}> und wird in das Meta-Record gemerged.
- Spätere Refinements gewinnen bei Konflikten (last-write-wins auf Key-Ebene).
- Derive([protocol | !protocol]) existiert; !Protocol negiert explizit.
(Quelle: 00-refinements.md §2, §4, §6)
Unklar / zu fixieren:
- Wie “Listen-/Set-artige” Labels gemerged werden (Dedup, Remove, Order, Canonicalisierung).
- Ob “deep merge” jemals existiert (für nested Records als Label-Wert) oder ob Label-Werte atomisch sind.
Lösung A (empfohlen): SetOps für bestimmte Label-Klassen
- Definiere eine Kategorie SetLabel: Wert ist eine Liste von Ops Add(X)/Remove(X) (syntaktisch X vs !X).
- Merge = Konkatenation der Ops (in Compose-Reihenfolge) + Normalisierung (Fold → finaler Set-Status).
- Output wird kanonisiert (z.B. sortiert oder “last-add order”) für Interning/Debugging.
Gilt für: derive, ähnliche “Feature/Derive”-Listen.
Lösung B: Replace-only
- Jede Liste wird als atomisch behandelt; spätere überschreibt frühere komplett.
=> Macht !X praktisch nutzlos (weil Removal ohne Kenntnis der Defaults schwierig wird).
2) “Meta setzt Verhalten” – Wo wird das wirksam?¶
Stand in den Specs (konsistent, aber verteilt):
- Compiler hat nur Sonderwissen über wenige Policies (Memory/Own/Share/Create/Drop).
- Unbekannte Meta-Keys werden nur gemerged; Framework/Makro/Tools interpretieren sie.
(Quelle: 00-refinements.md §5–§6)
- Funktions-Refinements wie Cache/Retry/Timeout/Transactional sind als typefns beschrieben.
(Quelle: 07-cast-effekte-refinements.md §3)
Unklar / zu fixieren:
- Wer erzeugt am Ende das Verhalten (IR/Lowering vs Runtime-Interpreter vs CallGraph/Effect-Engine).
- Welche Teile sind Debug-only interpretierbar vs Release “compiled”.
Lösung A (empfohlen, passt zu 07-cast): Build-time Lowering + Debug-Interpreter
- Debug: Refinement-Keys werden in einer (Stdlib-)Interpreter-Schicht ausgewertet (inspektierbar).
- Release: ein Generator/Plugin (nicht Core-Compiler-Sonderfall) liest Meta und lower’t es zu explizitem IR/Bytecode.
=> Entspricht 07-cast Idee: Debug interpretiert, Release bettet optimiert ein.
Lösung B: Runtime-only Interpretation
- VM/Runtime liest Meta bei jedem Call und entscheidet dynamisch.
=> Einfacher zu implementieren, aber Hot-Path-Kosten und schwerer zu verifizieren.
3) Funktions-Refinements: Reihenfolge, Scope, Keying (Cache/Retry/Timeout)¶
Stand in den Specs:
- Cache: Key = Funktionssignatur als Namespace + Hash(Params); Params müssen Hashable sein (statisch geprüft).
- Komposition: Reihenfolge der Ausführung ist festgelegt: Timeout → Retry → Cache → Funktion; “Compiler legt fest, nicht Entwickler”.
(Quelle: 07-cast-effekte-refinements.md §3.1, §3.5)
Unklar / zu fixieren:
- Cache-Scope (global? per CallGraph? per Service-Instanz? per Session/REPL?).
- Effect-Stil: Dependencies (Effect[..., D]) sind keine Parameter – wie fließen Service-Identitäten in den Cache ein, damit keine falschen Provider geteilt werden?
Lösung A (empfohlen): Cache ist Teil des Ausführungskontexts (Dictionary/CallGraph)
- Cache-Storage hängt am CallGraph/Effect-Runtime-Kontext (Dictionary-Design in 07-cast §5).
- Key = (function namespace, param hash, dependency identity hash aus dem Context).
=> Korrekt für unterschiedliche Provider, REPL-Sessions, Tests (einfacher Reset).
Lösung B: Cache ist global pro Funktion, Dependencies ignoriert
- Einfach, aber potentiell semantisch falsch (Provider-A/B teilen Cache).
=> Nur vertretbar, wenn D immer “global singleton” ist (selten).
4) Arenas, Reset ohne Traversal, Copy/Sharing¶
Stand in den Specs:
- with Storage.arena[...] eröffnet Arena-Scope; Reset ist Pointer-Reset (kein Traversal).
- Cross-Arena: Werte verlassen Arenas nur per Copy; Copyable/!Copyable existiert; Default-Copy ist rekursiv über TypeDescriptor + Handle-Map (Sharing-preserving, cycle-safe).
(Quelle: 03-runtime.md “Grundregeln” + “Copy-Mechanismus”)
- JME Policies: memory: ref, memory: arena[A], memory: pool[...] etc. Arenas leben bis reset().
(Quelle: 10-runtime-handles-und-memory-safety.md §X.4.3)
Unklar / zu fixieren (kritisch):
- Wie Reset “kein Traversal” mit Drop/Refcount zusammenpasst, wenn Arena-Objekte (direkt/indirekt) Handles auf memory: ref Objekte halten.
- Was genau “Copy” bei memory: ref bedeutet (retain vs deep-clone) – im Spec wird rekursives Copy beschrieben, aber die Policy-Abgrenzung ist nicht 100% explizit.
Lösung A (empfohlen, behält Reset=O(1)): Arena-Lifetime ist abgeschlossenes Universum
- In Arena gespeicherte Werte dürfen nur auf Werte zeigen, die ebenfalls in derselben Arena (oder Value) leben.
- Der Compiler/Verifier erzwingt: Keine “strong ref” aus Arena in globales memory: ref (oder nur als Weak).
- Copy-out erzeugt neue Objekte im Ziel-Backend (caller arena/ref) mit Handle-Map.
=> Reset bleibt Pointer-Reset ohne Refcount-Work.
Lösung B: Arena-Reset hat eine begrenzte “Release-Liste”
- Arena-Backend führt eine Liste externer strong refs (nur diese werden beim Reset decremented).
- Kein Full-Traversal, aber O(k) pro Reset, k = #externer refs.
=> Erlaubt mehr Flexibilität, aber Reset ist nicht mehr strikt O(1).
5) Callable/Closures: Was ist ein “Callable”, wie Captures funktionieren¶
Stand in den Specs:
- VM Calling Convention: Call dst, func, argsBase, argc; func ist “Callable (Proto/Closure/native)”.
- Closure + Upvalues existieren (Closure, LoadUp, StoreUp, CloseUp).
(Quelle: 05-vm-instruction-set.md §3.1, §6.7)
Unklar / zu fixieren:
- Capture-Regeln (by value / by ref), insbesondere für mutable Bindings.
- Wo Closure-Environment lebt (JME ref vs arena vs stack) und welche Policies daraus abgeleitet werden.
Lösung A (empfohlen): val by value, var via Cell/Handle by reference
- val capture: copy/retain nach Policy, env ist immutable record.
- var capture: env hält Cell[T] (ref/handle), Mutationen laufen über Cell.
=> Erwartbar, verhindert “var snapshot”-Überraschungen.
Lösung B: Alles by value (v1), var capture verboten
- Minimal, aber user-feindlich und später schwer rückwärtskompatibel.
6) Reifikation (reified) und TypeInfo.of[T]¶
Stand in den Specs (klar):
- Runtime-TypeInfo auf Typparameter nur mit reified; nie implizit.
(Quelle: 04-qualifier-effekte-konversionen-wire.md §2.3 + “Normative Invarianten”)
- Intrinsics liefern TypeId und Handle-Policies als Runtime-Snapshots.
(Quelle: 08-intrinsics-vm-api.md Gruppe 4)
Unklar / zu fixieren:
- Was TypeInfo konkret enthält (nur TypeId? Name? Fields? Meta?) und wie stabil das ist.
- In älteren Texten taucht TypeInfo.of[P]() ohne reified auf (semantisch nicht zulässig nach Qualifier-Spec).
Lösung A (empfohlen): TypeInfo = stabiler TypeId + Side-Table Lookup
- TypeInfo trägt mindestens TypeId; Name/Layout/Meta kommen über compile-time tables (debug/strip-able).
- Typchecker erzwingt reified für TypeInfo.of[T].
=> Konsistent, billig, gut für Tooling.
Lösung B: TypeInfo immer verfügbar, aber “voll” nur bei reified
- Ohne reified nur opaque id; mit reified zusätzliche Daten.
=> Weicher, aber widerspricht der starken Invariante “nie implizit”.
7) Span/Interpolation: Extrapolation vs Clamping, Driver-Vertrag¶
Stand in den Specs:
- Interpolatable.interpolate: t außerhalb [0,1] ist erlaubt (Extrapolation gültig).
- Span.at(t) nutzt interpolate direkt; stream/driveWith clampen t in [0,1].
(Quelle: 09a-spec-span-interpolation.md SP.3.1, SP.5.1, SP.7–SP.8)
Unklar / zu fixieren:
- Ob Clamping konfigurierbar ist (Clamp/Wrapping/Raw).
- Wie streamAt Dependency-Style final ausgedrückt wird (Effect vs Parameter) – semantisch: Driver trägt Effekt, Span bleibt pure.
Lösung A (empfohlen): at raw, Stream default clamp + optional Raw-Varianten
- Span.at bleibt extrapolationsfähig.
- stream/driveWith clampen per Default, aber biete driveWithRaw/streamRaw an.
=> Klar, konsistent mit Spec.
Lösung B: Alles clampen
=> Bricht explizit die Extrapolation-Invariante.
8) Block-Wertmodell / implicit return (Tail-Expr) + = expr Bodies¶
Stand in den Specs:
- VM hat Return src als Frame-Terminator (Runtime/Verifier-Seite).
(Quelle: 05-vm-instruction-set.md §6.6)
- Ein normatives, sprachseitiges Tail-Expr-Return-Modell ist in den aktuellen Specs nicht vollständig ausformuliert (teils ältere Beispiele mit return existieren).
Unklar / zu fixieren:
- “Block-Wert” Regeln: Wann ist ein Block Ausdruck? Was ist der Default (Unit)?
- def ... = expr Sugar: exakt gleiche Semantik wie { expr } und keine {} als RHS (deine Entscheidung).
Lösung A (empfohlen, minimal): Tail-Expr ist Return-Wert, sonst Unit
- Block evaluiert Statements; wenn letztes Item ein Expr ist → Return-Wert, sonst Unit.
- = expr lower’t zu Block mit Tail-Expr.
=> Einfache IR-Regel, gut für Verifier/Drop-Pläne.
Lösung B: Block hat immer Unit, Return nur explizit
=> Kollidiert mit deinem Designziel (expression-orientiert) und macht = expr weniger wertvoll.
Praktische “Abhak”-Checkliste (kurz)¶
- SetLabel/Derive Merge: Add/Remove (
!), Dedup, Canonicalisierung festgelegt. - Refinement-Interpretation: Debug-Interpreter vs Release-Lowering definiert (wer macht was).
- Cache-Scope + Dependency-Identity in Keying (Effect-Stil) definiert.
- Arena Reset vs Drop/Refcount: Entscheidung A oder B, inkl. Verifier-Regeln.
- Closure Captures:
val/varCapture-Regel + Env-Storage festgelegt. -
reifiedenforcement +TypeInfoMinimalrepräsentation (TypeId + side tables) festgelegt. - Span: extrapolation vs clamp (default + opt-in raw) festgelegt.
- Blockwert/Tail-Expr +
= exprLowering normativ fixiert.