JDL Spec — Namespaces & Modulsystem¶
Status: Draft (konsolidiert, Syntax an den aktuellen JDL-Stand angepasst)
Scope: Module als Namespaces,::-Auflösung, Import/Export,mod.jdl-Re-Exports, Konfliktregeln, relative Pfade (parent::,package::), Fully-Qualified Names, reservierte Namespaces und Prelude.
Out of scope: Paketmanager, Versionierung, Diamond-Resolution, Build-System.
1. Begriffe¶
- Symbol: Name einer Definition (z.B.
User,findUser). - Modul: Quelltext-Einheit, die über einen Dateipfad identifiziert wird.
- Namespace: Ein Namensraum, in dem Symbole eindeutig adressierbar sind. In JDL sind Module Namespaces.
- Qualified Name: Name mit Namespace-Pfad, z.B.
users::service::findUser. - Fully Qualified Name (FQN): Qualified Name ab Root, ohne implizite Präfixe.
- Package: Logische Distributions-Einheit (für Coherence/Orphan-Regel relevant). Details sind Paketmanager-Sache, aber „gleiches Package/Modul“ ist eine normative Grenze.
2. Separator-Regel: :: vs .¶
::ist der Namespace-Separator (Module, Type-Namespaces, Intrinsics-Namespaces)..ist Member-/Property-Zugriff (z.B.user.email,vec.len()), nicht für Namespaces.
Normativ: Namespaces werden nie mit . getrennt.
3. Modul- und Namespace-Identität¶
3.1 Dateipfad → Modulname¶
- Der Dateipfad ist der Modulname.
- Eine Datei
users/service.jdldefiniert das Modulusers::service. - Es gibt keine Moduldeklaration innerhalb der Datei; der Pfad ist die Quelle der Wahrheit.
3.2 Zirkuläre Abhängigkeiten¶
- Zirkuläre Modulabhängigkeiten sind ein harter Compilerfehler.
4. Sichtbarkeit¶
4.1 Sichtbarkeitsstufen¶
- Ohne Sichtbarkeits-Keyword ist eine Definition privat (nur innerhalb des Moduls sichtbar).
- Mit
packageist sie in allen Modulen desselben Packages/Plugins sichtbar, aber nicht extern exportierbar. - Mit
pubist sie exportiert und für andere Module importierbar (direkt oder via Re-Export).
Beispiel:
// users/service.jdl
def validateEmail(email: str) -> bool { ... } // privat, nur innerhalb des Moduls verwendbar
package def normalizeEmail(email: str) -> str
pub def findUser(email: str) -> Result[User, UserError]
pub type User: struct { ... }
pub type UserError: enum = | NotFound | AlreadyExists
5. Imports¶
5.1 Import-Formen (kanonisch)¶
Modul importieren (als Modulname im Scope):
Symbole aus einem Modul importieren:
import users::service : User, findUser
import users::service : User as AppUser
import std::crypto : Hasher as enc
Normativ:
- Es gibt keinen impliziten „Wildcard“-Import. Explizit ist Pflicht.
- Wenn eine Importform import a::b::c : Y, Z nutzt, ist das die bevorzugte Schreibweise für „gezielt und lesbar“.
5.2 Relative Auflösung mit parent:: und package::¶
parent::referenziert den Parent-Namespace des aktuellen Moduls.package::referenziert das Root-Modul des aktuellen Packages/Plugins.
Beispiele:
// users/mod.jdl
pub import package::model : User, UserId
pub import package::service : findUser, registerUser
// users/http/router.jdl
import parent::middleware : Middleware
import package::model : User, UserId
Normativ:
- parent::x und package::x sind relative Namespace-Referenzen.
- parent:: darf nur verwendet werden, wenn ein Parent-Namespace existiert. Die Verwendung von parent:: in einem Top-Level-Modul ist ein Compilerfehler.
- package:: ist immer relativ zum Root-Modul des aktuellen Packages/Plugins.
5.3 Importbarriere für internal¶
- Ein Modulpfad der ein Segment
internalenthält, ist außerhalb des aktuellen Packages/Plugins nicht importierbar. pubin eineminternal-Modul bedeutet daher: sichtbar innerhalb des Packages/Plugins, nicht automatisch extern importierbar.
Beispiele:
Normativ:
- import users::internal::validator : normalizeEmail ist innerhalb desselben Packages/Plugins zulässig.
- Derselbe Import ist außerhalb des Packages/Plugins ein Compilerfehler.
- mod.jdl bleibt API-Shaping für das Root-Modul; internal ist die harte Importbarriere.
5.4 Lokale Imports¶
- Imports dürfen innerhalb von Funktionen vorkommen.
def parseConfig(path: str) -> Result[Config, ParseError] {
import std::formats::toml : parse
parse(path)
}
5.5 Einschränkung: keine lokalen Imports von service-Typen¶
- Lokale Imports von Service-Typen sind verboten.
- Services werden als explizite Parameter (imperativer Stil) oder über
Effect[R, E, D]im Rückgabetyp (Effect-Stil) deklariert — nie per lokalem Import.
Beispiel (muss fehlschlagen):
def findUser(email: str) -> Result[User, UserError] {
import myapp::services : UserRepo // COMPILERFEHLER: service-Typ lokal importiert
...
}
5.6 Tiny Prelude¶
- JDL kennt einen kleinen impliziten Prelude.
- Dieser Prelude ist kein impliziter Wildcard-Import von
jdl::core. - Der Compiler bringt nur eine bewusst kleine Menge kanonischer Kern-Typen automatisch in Scope.
Normativ:
- Result und Option sind kanonisch in jdl::core definiert.
- Der Compiler bringt Result und Option als bootstrap-preloaded Teilmenge automatisch in Scope, ohne dass ein expliziter Import nötig ist.
- Primitive Builtins (i8..i64, u8..u64, f32, f64, bool, str, ()) sind keine normalen Prelude-Imports, sondern Sprach-Builtins.
6. Fully Qualified Names (FQN) und „Full Qualified Imports“¶
6.1 Fully Qualified Usage (ohne Import)¶
Ein Symbol darf per FQN verwendet werden, ohne es vorher ins lokale Scope zu importieren:
let u = users::service::findUser("a@b.c")
type UserUpdate = std::typefn::Partial[users::model::User, [.id, .name]]
Normativ: - Der Compiler muss FQN-Referenzen als Abhängigkeiten im Modulgraphen erfassen (wie bei Imports).
6.2 „Full Qualified Import“ (Symbol direkt importieren)¶
Als sugar darf ein Symbol auch direkt über seinen vollständigen Pfad importiert werden:
Semantik:
- import A::B::x ist äquivalent zu import A::B : x (ggf. mit Alias).
7. mod.jdl — optionale API-Kontrolle (Re-Exports)¶
7.1 Semantik¶
mod.jdlist eine normale JDL-Datei mit folgender Konvention:dir/mod.jdlrepräsentiert das Moduldir.- Sie dient dazu, die public API des Root-Moduls über Re-Exports zu formen.
Normativ:
- Ohne mod.jdl: Das Root-Modul des Ordners exportiert alle pub-Definitionen, als wären sie Teil seiner öffentlichen Oberfläche.
- Mit mod.jdl: Die Re-Exports in mod.jdl definieren die öffentliche Oberfläche des Root-Moduls.
Wichtig:
- mod.jdl ist API-Shaping für das Root-Modul.
- Es ist keine Zugriffskontrolle gegen direkte Imports von Submodulen. Ein Submodul kann weiterhin via import users::validator importierbar sein, wenn es dort pub-Symbole gibt (sofern nicht anders spezifiziert).
Beispiel:
// users/mod.jdl
pub import package::model : User, UserId
pub import package::service : findUser, registerUser
// validator.jdl bleibt nicht Teil der Root-API, ist aber ggf. direkt importierbar
7.2 Transitive Sichtbarkeit (Re-Exports)¶
- Re-Exports sind transitiv sichtbar.
- Ein Konsument muss das Originalmodul nicht separat importieren.
// users/mod.jdl
pub import std::types : UserId
// anderswo
import users : User, UserId // UserId kommt transitiv aus std::types
8. Namensauflösung und Konflikte¶
8.1 Konfliktregel (unqualifiziert)¶
- Wenn zwei Imports dasselbe Symbol ins unqualifizierte Scope bringen, ist das ein Compilerfehler.
import std::collections : Map
import mylib::types : Map // COMPILERFEHLER: Map kollidiert
import mylib::types : Map as MyMap // ok
¶
import std::collections : Map
import mylib::types : Map // COMPILERFEHLER: Map kollidiert
import mylib::types : Map as MyMap // ok
9. Reserved Namespaces¶
9.1 Stdlib- und System-Namespaces¶
jdl::ist der user-facing Root-Namespace der JDL-Stdlib.jade::ist der reservierte interne System-/Runtime-Namespace für privilegierte JDL-Systemmodule.
9.2 Compiler-provided Namespace: @compiler¶
@compilerist ein compiler-provided Namespace.- Er liefert compile-time Queries/Primitives, die nicht sauber als normale Library ausgedrückt werden können, z.B.:
- Target/Build/Feature-Fakten:
@compiler::target::*,@compiler::build::*,@compiler::feature("x") - Layout/Type Queries (später):
@compiler::sizeof(T),@compiler::alignof(T),@compiler::layoutof(T)
Normativ:
- Namespaces mit @-Prefix sind für Compiler/Toolchain reserviert.
- @intrinsics ist kein kanonischer Namespace mehr; bestehende Verwendungen sind historische Altlasten und auf @compiler zu normalisieren.
10. Zusammenhang mit provide (Coherence / Orphan-Regel)¶
Damit provide P for T nicht zur Import-Lotterie wird:
- Eine Protocol-Implementierung ist nur erlaubt, wenn P oder T im selben Package oder Modul definiert ist.
- Für fremde Typen nutzt man Newtypes.
11. Offene Punkte (bewusst minimal gehalten)¶
- Soll es zusätzlich zu
parent::undpackage::weitere relative Segmente geben? Aktueller Stand: nein. - Falls ein projektweiter Opt-out vom Tiny Prelude gewünscht ist, muss die Manifest-/Compiler-Flag-Seite spezifiziert werden.
internalist als harte package-/plugin-interne Importbarriere normiert; offene Folgefrage bleibt nur, ob zusätzlich weitere Zugriffsstufen nötig sind.