Pureline Emvus
Aktuelles Emvus-Design: method, Result, scoped, Data Classes.
Pureline Emvus folgt dem **aktuellen Emvus-Sprachdesign**. Es ist ein Stilprofil für die beabsichtigte `.ev`-Syntax, keine Behauptung, dass jedes gezeigte Feature bereits von Ring 1 implementiert ist.
Dieses Profil ist dokumentiert, wird vom Browser-Checker aber noch nicht automatisch geprüft.
Profilregeln
Native Methodendeklarationen
Methoden werden mit method deklariert, bei Bedarf mit Rückgabetyp in method<Type>. Generische Typvariablen werden über where eingeführt, nicht durch eine zweite Generic-Parameterliste am Methodennamen.
Eine Methode ohne expliziten Rückgabetyp gibt Void zurück.
method<T> first<T>(List<T> items) {
return items.get(0)
} method<Int> square(Int value) {
return value * value
}
method<T> first(List<T> items) where T {
return items.get(0)
} Leere Deklarationsklammern optional, Aufrufklammern nicht
Beide parameterlosen Deklarationsformen sind Pureline-konform. Sie deklarieren Methoden ohne Argumente; der Unterschied ist kosmetisch, **kein** Overload-Unterschied.
Zum Ausführen wird immer refresh() oder shutdown() geschrieben; object.refresh ist kein Methodenaufruf. Formatter bewahren oder konfigurieren beide Deklarationsstile konsistent, ohne den jeweils anderen als Fehler zu markieren.
cache.refresh method refresh {
updateCache()
}
method shutdown() {
closeConnections()
} Braces Pflicht, Semikolons optional
Für if, Schleifen und vergleichbaren Kontrollfluss werden explizite { ... }-Blöcke verwendet. Optionale Semikolons werden im idiomatischen Emvus weggelassen, bestehender gültiger Code mit Semikolons wird aber nicht abgelehnt.
Inline-Guards ohne Braces und unnötige else-Blöcke sind nicht Pureline. Fortsetzungszeilen und lange Ausdrücke bleiben eindeutig.
if (value < 0) return method process(Int value) {
if (value < 0) {
return
}
println(value)
} Naming, Imports und Sichtbarkeit
Typen: PascalCase; Interfaces: I-Prefix (IStorage, IConnection). Methoden, Felder, Properties, Parameter und lokale Variablen: camelCase. Konstanten: UPPER_SNAKE_CASE. Packages: kleingeschriebene Punktpfade.
Quelldateinamen sind **camelCase**, z. B. sessionService.ev, auch wenn die primäre Klasse SessionService heißt.
Die Standardsichtbarkeit ist public; private oder protected wird bei Bedarf angegeben.
Die Symbolauflösung ist case-insensitiv, die Schreibweise bleibt aber erhalten: die **deklarierte Schreibweise** wird konsistent verwendet, und es werden keine Namen definiert, die sich nur in der Groß-/Kleinschreibung unterscheiden.
Explizite Imports und Aliase ersetzen das Verlassen auf verwirrende Kollisionen.
package app.session
use {
std.collections.[IList, IIterator],
app.storage.userStorage as Storage
} Typen sollen Absicht vermitteln
Emvus ist standardmäßig statisch typisiert. var führt **statische Typinferenz** durch; dynamic fordert ausdrücklich laufzeitgetyptes Verhalten an.
dynamic wird an wirklich dynamischen Grenzen verwendet, nicht um normale Typprüfungen zu umgehen. Explizite und inferierte statische lokale Typen sind beide Pureline-konform.
Person person = Person("Jason", 20)
var current = person.copy()
dynamic payload = receiveUnknownPayload() Nullability über stabilen Kontrollfluss behandeln
Emvus verlangt keine T?-Annotation, sondern verfolgt Null-Zustände über den Kontrollfluss. Ein möglicherweise null-wertiger Wert wird früh abgesichert, und von veränderlichen Properties wird vorher ein stabiler lokaler Snapshot genommen.
Man verlässt sich nie darauf, dass wiederholte, unsynchronisierte Lesezugriffe auf eine veränderliche Property denselben Wert liefern. Erforderliche Feldinitialisierung wird explizit gemacht.
method<String> displayName(Profile profile) {
if (profile.nickname == null) {
return "Unknown"
}
return profile.nickname
} method<String> displayName(Profile profile) {
final var nickname = profile.nickname
if (nickname == null) {
return "Unknown"
}
return nickname
} Typisierte Result-Behandlung für erwartete Fehler
Emvus hat keine try/catch/finally-Syntax. Es werden Result<T, E>, success, fail, ? und switch so verwendet, wie die Sprache es vorsieht.
? dient der bewussten Fehlerweitergabe, wo die Typen es erlauben. ! löst bei einem Fehler einen Panic aus und bleibt etablierten Invarianten oder Grenzprüfungen vorbehalten, die sich wirklich nicht beheben lassen.
Ein Result wird nicht verworfen, und erwartete Fehler werden nicht hinter panic versteckt. success, fail und return bleiben getrennt, statt jede Operation unnötig zu verpacken.
method<Int> validatePort(Int port) {
try {
check(port)
} catch (RangeException e) {
println(e)
}
} method<Result<Int, RangeException>> validatePort(Int port) {
if (port < 1 || port > 65535) {
fail RangeException(port, 1, 65535)
}
success port
}
method showPort(Int port) {
switch (validatePort(port)) {
success(value) -> println(value)
error(problem) -> errprintln(problem.message)
}
} Ressourcenlebensdauer mit `scoped` sichtbar machen
Für Ressourcen, die am Ende ihres Scopes geschlossen werden müssen, wird Emvus' scoped-Konstrukt verwendet.
scoped wird nicht durch Java-try-with-resources, Go-defer oder ein nicht existierendes Emvus-finally ersetzt. Die Lebensdauer einer Ressource bleibt klar, auch wenn der Compiler den Speicher optimiert.
method<String> readFile(String path) {
scoped var file = Files.open(path)
return file.readAll()
} Data Classes statt Boilerplate
data class wird für kleine, benannte, datenzentrierte Typen mit dem eingebauten generierten Verhalten verwendet.
Primäre Data-Properties sind außerhalb der Initialisierung schreibgeschützt. copy() und withX(...) drücken veränderte Kopien aus und sind standardmäßig flach. Generierte Gleichheit, Hashing und Textdarstellung werden, wo passend, dem manuellen Neuschreiben vorgezogen. Größere, verhaltensreiche Typen bleiben reguläre Klassen.
**Es gibt keine separate record-Stilvorgabe:** das vereinbarte Emvus-Design nutzt data class für benannte Datenmodelle und Dictionary für Dictionary-/Objektliteral-Daten.
data class Member(String name, Int age)
data class Server(String host, Int port = 25565) Listen, Tupel und Dictionaries behalten ihre Semantik
Bei [...]-Literalen bedeutet **exakt gleicher Typ aller Elemente** eine List, sonst ist der Ausdruck ein Tupel. Pureline überschreibt diese typgetriebene Regel nicht durch eine Stilpräferenz.
Dictionary-Literale verwenden { key: value }; inferierte gemischte Werttypen nutzen dynamic, wo das Sprachmodell es verlangt. Auf Dictionaries wird über **Schlüssel** zugegriffen, nicht über Record-Punktnotation.
Ein wechselnder Elementtyp kann die Inferenz zwischen Liste und Tupel legitim ändern. Dieses Verhalten bleibt an API-Grenzen, wo passend, durch explizit deklarierte Typen sichtbar.
println(server.name) var numbers = [10, 20, 30] // List<Int>
var response = [200, "Success"] // [Int, String] var server = {
"name": "Emvus",
"port": 25565
}
println(server["name"]) Destructuring ist positional
{ ... } destrukturiert Tupel- oder Data-Class-Komponenten in Deklarationsreihenfolge. _ kann ein Element überspringen.
Destructuring wird nicht als benanntes Record-Matching umgedeutet. Kurze, aussagekräftige Bindungen sind wahlloser Entpackung vorzuziehen.
var { status, message } = [200, "OK"]
var { first, _ } = loadPair() `switch` soll vollständige Verzweigung zeigen
switch-Anweisungen oder -Ausdrücke werden für echte Zustandsalternativen verwendet. Ausdrücke sind exhaustiv oder haben einen definierten Catch-All-/Failure-Arm.
Für Result sind klare success(...)- und error(...)-Muster verschachtelten Bedingungen vorzuziehen. Catch-All-Muster bleiben bewusst und sind kein Weg, vergessene Fälle zu verstecken.
method<String> rankLabel(Rank rank) {
return switch (rank) {
MEMBER -> "Member"
MODERATOR -> "Moderator"
ADMIN -> "Admin"
}
} Cascades nur für einen einzelnen Receiver
Cascades mit .. eignen sich für eine kurze Folge von Operationen auf demselben Receiver.
Ketten, die unzusammenhängende Effekte verbergen, mehrere Objekte implizit verändern oder sich über viele Anweisungen erstrecken, werden vermieden.
Person person = Person("Anna", 20)
..birthday()
..age = 21 Fibers und Threads brauchen klare Lebenszyklen
fiber {}, virtual {} und thread {} sind unterschiedliche Ausführungskonstrukte mit einem gemeinsamen Task-Ergebnismuster. Gewählt wird nach der tatsächlichen Arbeitslast, nicht weil Nebenläufigkeit kompakt aussieht.
Ergebnisse und Fehler, die zählen, werden awaited. Abbruch und Ownership langlebiger Tasks sind klar. Geteilter veränderlicher Zustand wird synchronisiert; @Synchronized steht bereit, wo passend.
Es wird nicht angenommen, dass ein vom Task erfasstes Objekt stack-lokal ist oder freigegeben werden kann, wenn die startende Methode zurückkehrt. Verstecktes Fire-and-forget in gewöhnlichen Business-Methoden wird vermieden.
var task = fiber {
return calculate(10)
}
Int result = task.await() Macros sollen Auffindbarkeit verbessern, nicht verschleiern
Emvus-Macros sind Compile-Time-AST-Transformationen mit Zieldeklarationen, quote, $(...)-Substitution und AST-Builder-APIs.
Pureline verlangt: kleine Macros mit klar benannter Verantwortung und unterstützten Zielen; deterministische Expansion, bei der Reihenfolge und entstehende Member auffindbar bleiben; Hygiene gegen versehentliche Namenskollisionen; Compiler-Diagnosen bei widersprüchlichem oder ungültigem expandierten Code; gewöhnliche Methoden statt Macros, wenn kein substanzieller Compile-Time-Nutzen besteht; keine Abhängigkeit von ungeprüftem Macro-Time-I/O, Netzwerk, Uhr oder Zufall.
Generierter Code muss semantische Prüfungen bestehen; Checker-/Formatter-Tooling behandelt generierte Stellen nicht wie gewöhnlich handgepflegte Dateien.
macro Trace on Method {
target.body.prepend(quote statement {
println("Method entered")
})
} Nicht gegen HyperAlloc-Flow-Interna programmieren
Emvus-Code darf über Sublang, Optimind und HyperAlloc Flow optimiert werden. Entwickler drücken **Semantik und Lebensdauern** aus und raten nicht manuell eine künftige Allokationsstrategie.
Geradlinige Objektlebenszyklen und stabile Ownership sind zu bevorzugen, geteilter veränderlicher Zustand bleibt explizit, besonders über Tasks hinweg. Für einfache Werte sind unveränderliche Data Classes zu bevorzugen, wo sie zur Domäne passen.
APIs werden nicht um die Annahme gebaut, ein Objekt sei immer stack-allokiert, scalar-replaced oder gepoolt. Allokation und Escape-Verhalten in Hot Paths werden durch Messung und Diagnose bewertet, nicht durch Folklore.
Package-Dichte und Source-Organisation
Ein Package-/Source-Ordner enthält standardmäßig höchstens **8** produktive .ev-Module. Tests, generierte Quellen und reine Metadaten-Dateien zählen nicht mit. Aufgeteilt wird nach tatsächlicher Feature-Verantwortung, nicht durch mechanisches Anlegen beliebiger Subpackages.
Package-Name und Dateiname sind verschiedene Konzepte: app.session bleibt kleingeschrieben, sessionService.ev ist camelCase.
Sprachnative Regeln schlagen sprachübergreifende Nachahmung
Pureline-Tooling meldet keinen Verstoß, nur weil Emvus sich von Java, Rust oder Go unterscheidet:
Optionale () in **parameterlosen Methodendeklarationen** sind gültig, Aufrufe brauchen immer (). Optionale Semikolons sind gültig. where ist die native Generic-Deklarationssyntax. var ist statisch inferiert, dynamic ist ausdrücklich dynamisch. success, fail, ?, !, scoped und AST-Macros haben Emvus-spezifische Bedeutungen. { ... } hat gültige kontextabhängige Verwendungen, darunter Dictionaries und positionales Destructuring. [ ... ] wird nach Emvus' Regel der exakt gleichen Elementtypen als Liste oder Tupel typisiert.
Der Formatter ist AST-bewusst, bewahrt die Bedeutung und wendet nie ::-Namespaces oder eine Semikolonpflicht aus anderen Sprachen auf .ev-Dateien an.