8.5 KiB
Die bisherige Diskussion hat sich um die Frage gedreht: "Was ist theoretisch sicher?". Du hast die Perspektive entscheidend auf die Frage gelenkt: "Was ist praktisch nützlich und führt zu einer hohen Akzeptanz bei der Zielgruppe?"
Ein System, das einen Domänen-Experten zwingt, ein funktionierendes, intuitives Muster in ein kompliziertes, für ihn abstraktes Muster umzubauen, nur um einen Compiler zufriedenzustellen, hat seinen Zweck verfehlt. Der Nutzer wird frustriert aufgeben.
Deine Schlussfolgerung ist daher richtig: Die "safe by design"-Garantie in ihrer strengsten Form (als harter Compiler-Fehler) ist für diesen Anwendungsfall ein Hindernis. Wir müssen das Design weiterentwickeln.
Von "Guaranteed Safety" zu "Guided Safety"
Die Lösung ist nicht, die Sicherheitsanalyse abzuschaffen, sondern ihre Konsequenz zu ändern. Der Compiler sollte nicht als sturer Torwächter agieren, sondern als intelligenter Assistent, der den Nutzer aufklärt und ihm eine bewusste Entscheidung ermöglicht.
Der neue Ansatz:
- Der Compiler erkennt das potenziell unsichere Muster (eine Closure fängt eine
var-Variable und verlässt ihren Scope) genau wie bisher. - Statt eines harten Fehlers erzeugt er eine detaillierte Warnung.
- Diese Warnung erklärt das Risiko präzise und für Laien verständlich: "Diese Funktion ist nicht threadsicher. Für isolierte Berechnungen wie Backtests ist das in Ordnung. Wenn Sie sie aber zwischen Threads teilen, kann es zu Fehlern kommen."
- Die Warnung schlägt die Lösung vor: "Wenn Sie dies beabsichtigen, kennzeichnen Sie die Funktion explizit als nicht-threadsicher, indem Sie
fndurchfn!ersetzen." - Der Nutzer ändert
fnzufn!. Die Warnung verschwindet. Der Nutzer hat gelernt, eine bewusste Entscheidung getroffen und kann weiterarbeiten, ohne seinen Code komplett umstrukturieren zu müssen.
Dies erreicht das Beste aus beiden Welten: Sicherheit wird nicht aufgegeben, sondern der Nutzer wird über die Risiken aufgeklärt und muss die Verantwortung explizit übernehmen.
Ich fasse das Ergebnis in einem neuen Projektplan-Eintrag zusammen.
Projektplan: "Guided Safety" und der fn!-Modifikator
- Datum: 09. Oktober 2025
- Uhrzeit: 17:47
Motivation
Die strikte "safe-by-design"-Garantie, die das "Escaping" von var-Variablen durch einen Compiler-Fehler verhindert, erweist sich für die Zielgruppe der programmiertechnisch unerfahrenen Domänen-Experten als zu restriktiv. Sie zwingt zur Umstrukturierung von Code, der im primären Anwendungsfall (isolierte Single-Thread-Berechnungen wie Backtests) sowohl korrekt als auch performant ist. Dies führt zu Frustration und mindert die Akzeptanz der Sprache.
Ziel
Das Sicherheitsmodell der Sprache wird von einer rigiden "Guaranteed Safety" zu einer benutzerfreundlichen "Guided Safety" weiterentwickelt. Der Compiler soll den Nutzer auf potenzielle Gefahren hinweisen, ihn aufklären und ihm ermöglichen, eine explizite, informierte Entscheidung zu treffen, anstatt ihn mit einem harten Fehler zu blockieren.
Ergebnis
-
Compiler-Warnung statt Fehler: Der Binder wird so angepasst, dass das Fangen einer
var-Variable durch eine "escapende" Closure nicht mehr zu einem Compiler-Fehler, sondern zu einer detaillierten Warnung führt. -
Einführung von
fn!: Ein neuer Funktions-Deklarations-Syntax(fn! ...)wird eingeführt. Dieser Modifikator signalisiert dem Compiler: "Der Programmierer hat die Warnung zur Kenntnis genommen und deklariert diese Closure absichtlich als potenziell nicht-threadsicher." Das Vorhandensein vonfn!unterdrückt die entsprechende Compiler-Warnung. -
Pädagogische Fehlermeldung: Die Warnmeldung wird so formuliert, dass sie den Sachverhalt erklärt und direkt die Lösung (
fn!verwenden) vorschlägt. Beispiel:Warnung: Die Funktion erfasst die veränderliche Variable 'sum', die außerhalb dieses Bereichs definiert wurde. Dies macht die Funktion nicht threadsicher. Für isolierte Berechnungen (z.B. in einem Backtest) ist dies unproblematisch und performant. Um zu bestätigen, dass dies beabsichtigt ist, ändern Sie
(fn ...)zu(fn! ...)." -
Beibehaltung der sicheren Muster: Die bestehenden, sicheren Muster (
atomfür geteilten Zustand,varmit zustandslosen Funktionen) bleiben die empfohlenen Standardlösungen.fn!dient als explizite, bewusste Abweichung für den gut verstandenen Performance-Fall.
Jetzt weiß der Compiler, ob eine Closure unsafe ist. Kann er sonst noch was mit dieser Information anfangen?
Ja, absolut. Das ist eine hervorragende Frage, die den Kern von modernem Compiler-Design trifft. Die Information, ob eine Closure potenziell unsicher ist, ist weit mehr als nur ein Schalter zum Unterdrücken einer Warnung – es ist ein wertvolles Metadatum, eine semantische Information, die das gesamte Ökosystem nutzen kann.
Stell dir das ! bei fn! wie einen "Gefahrgut"-Aufkleber auf einem Paket vor. Es sagt nicht nur dem Absender "Vorsicht", sondern informiert die gesamte Lieferkette (Compiler, Laufzeitumgebung, andere Entwickler) darüber, wie dieses Paket zu behandeln ist.
Hier sind konkrete, extrem nützliche Dinge, die der Compiler und die Laufzeitumgebung mit dieser Information tun können:
1. Laufzeit-Überprüfung im Debug-Modus 💥
Das ist der größte Gewinn. Wenn der Compiler weiß, dass eine fn!-Closure erzeugt wird, kann er speziellen Code für den Debug-Build generieren:
- Beim Erstellen der Closure: Die Laufzeitumgebung speichert die
ThreadIDdes erstellenden Threads direkt in der Closure-Struktur. - Bei jedem Aufruf der Closure: Die Laufzeitumgebung prüft:
if CurrentThreadID != CapturedThreadID then. - Wenn die IDs nicht übereinstimmen, wird sofort eine harte Exception ausgelöst mit einer extrem klaren Fehlermeldung:
"Laufzeitfehler: Die nicht-threadsichere Funktion, die in Thread A (ID: 1234) erstellt wurde, wurde fälschlicherweise von Thread B (ID: 5678) aufgerufen. Dies hätte zu einer Race Condition geführt."
Der Vorteil ist gigantisch: Ein unvorhersehbarer, schwer zu findender Heisenbug (eine Race Condition, die nur manchmal auftritt) wird in einen 100% reproduzierbaren, sofortigen Absturz mit perfekter Fehlerbeschreibung umgewandelt. Allein dieses Feature macht fn! von einer "gefährlichen" zu einer extrem nützlichen, fehlertoleranten Abstraktion.
2. Erweiterte Statische Analyse (Linting) 🧐
Der Compiler (oder ein separates Linter-Tool) kann diese Information für intelligentere Code-Prüfungen nutzen. Er kann die "Gefahrgut-Pakete" verfolgen:
Angenommen, es gibt eine Funktion (parallel-map ...) in der Standardbibliothek, die dafür bekannt ist, ihre Arbeits-Funktion auf mehreren Threads auszuführen. Die Signatur dieser Funktion kann intern vermerken, dass sie nur threadsichere Closures akzeptiert.
Wenn ein Entwickler nun versucht, Folgendes zu schreiben:
(let my-unsafe-sma (create-sma-unsafe 20)) ; Gibt eine fn!-Closure zurück
(parallel-map my-unsafe-sma my-big-data-list)
Kann der Compiler an dieser Stelle eine noch spezifischere und ernstere Warnung oder sogar einen Fehler ausgeben:
"Typenkonflikt:
parallel-maperwartet eine threadsichere Funktion, abermy-unsafe-smawurde mitfn!als nicht-threadsicher deklariert. Dies führt garantiert zu Datenkorruption."
3. Automatische API-Dokumentation 📖
Ein Dokumentations-Generator kann die fn!-Metadaten auslesen und automatisch Warnhinweise in die generierte HTML-Doku einfügen. Wenn jemand die Dokumentation für deine CreateSMA-Funktion liest, könnte dort stehen:
CreateSMA(len: Integer): Function
Erzeugt eine Funktion zur Berechnung eines gleitenden Durchschnitts.
⚠️ Thread-Sicherheit: Diese Funktion gibt eine optimierte, nicht-threadsichere Funktion (
fn!) zurück. Das Ergebnis darf unter keinen Umständen zwischen verschiedenen Threads geteilt werden.
Dadurch wird der API-Vertrag für jeden sofort ersichtlich, ohne dass man den Quellcode lesen muss.
Fazit
Die explizite Kennzeichnung mit fn! ist also kein Rückschritt in der Sicherheit. Im Gegenteil, es ist eine semantische Anreicherung. Sie verwandelt das Konzept von einer reinen Warnungs-Unterdrückung in einen expliziten Vertrag, den die gesamte Werkzeugkette – Compiler, Debugger, Linter, Doku-Generator – versteht und nutzen kann, um den Entwickler noch besser und intelligenter zu leiten.