3 Punkte von GN⁺ 2025-11-01 | 1 Kommentare | Auf WhatsApp teilen
  • John Carmack hat seine persönliche Sicht auf die Verwendung veränderlicher Variablen (mutable variables) geteilt
  • Er sagte, dass er beim Arbeiten mit Python das Prinzip der „einmaligen Zuweisung“ (single assignment) vernachlässigt habe und sich selbst davor warnen müsse
  • Er betonte, dass man außer bei iterativen Berechnungen in Schleifen Neuzuweisungen oder Aktualisierungen von Variablen vermeiden sollte
  • Wenn alle Zwischenschritte der Berechnung erhalten bleiben, hilft das beim Debugging, und beim Verschieben von Codeblöcken kann es verhindern, dass unbeabsichtigt frühere Werte verwendet werden
  • In C/C++ sei es eine gute Gewohnheit, fast alle Variablen bereits bei der Initialisierung als const zu deklarieren
  • Abschließend betonte er mit den Worten „Ich wünschte, mutable wäre ein Keyword“, dass Unveränderlichkeit der Standard sein sollte

1 Kommentare

 
GN⁺ 2025-11-01
Hacker-News-Kommentare
  • Nachdem ich 2 Jahre lang Clojure verwendet habe, wurde mir klar, wie schwer es ist, anderen Entwicklern die Klarheit zu vermitteln, die Unveränderlichkeit bringt
    Wer daran gewöhnt ist, Effekte durch Zustandsänderungen zu erzeugen, versteht das meist erst, wenn er es selbst erlebt

    • Variablenänderungen erzeugen implizite Abhängigkeiten von der Reihenfolge
      Wenn man zum Beispiel x = 7; x = x + 3; x = x / 2 schreibt, gibt es beim Vertauschen der Reihenfolge keinen Fehler, aber das Ergebnis ändert sich
      Verwendet man dagegen neue Variablen wie x1 und x2, führt eine falsche Reihenfolge zu einem Fehler und macht das Problem klar sichtbar
      Letztlich ist Single Assignment eine Möglichkeit, solche Abhängigkeiten explizit auszudrücken
    • Ich hatte in Scheme eine ähnliche Erfahrung
      Selbst wenn ich Kollegen ohne Erfahrung mit funktionalen Sprachen erklärt habe, wie leicht zu testen und sauber funktionszentriertes Denken ist, kam das kaum an
      In Python lässt sich ein funktionaler Stil nur schwer lesbar schreiben, und JS wirkt dafür sogar besser geeignet
      Am Ende probieren nur neugierige Entwickler Sprachen wie Clojure aus
    • Wenn unveränderliche Daten und pure Funktionen der Standard sind, kann man Funktionen wie vollständige Blackboxes behandeln
      Die Funktion muss keinen externen Zustand kennen, und außen muss niemand das Innere der Funktion kennen
      Man kann eine bestimmte Funktion unabhängig testen oder debuggen, ohne den Gesamtzustand des Programms zu kennen
    • Unveränderlichkeit und Veränderlichkeit einfach gegeneinanderzustellen, ist eine Art Ausweichen vor Komplexität
      Es ist interessant zu sehen, wie die Haskell-Community am Ende versucht, Veränderlichkeit innerhalb des Typsystems neu zu erfinden
      Entscheidend ist, Nebenwirkungen mit minimalen Kosten unter Kontrolle zu halten
    • Clojure war von allen Sprachen, die ich gelernt habe, die einflussreichste Sprache
      Haskell hatte wegen seines Typsystems eine hohe Einstiegshürde, und F# war zu kompromissbereit, sodass man am Ende in C#-Syntax codiert
      Dank der Homoikonizität von Clojure und seiner mächtigen Datenstrukturen wurde mir das Konzept, „mit Werten zu arbeiten“, zum ersten Mal wirklich klar
      Beruflich würde ich es wohl nicht einsetzen, aber Menschen ohne Erfahrung mit funktionalen Sprachen oder Lisp würde ich es unbedingt empfehlen
  • Ich wünschte, Variablen wären standardmäßig unveränderlich und alles wäre ein Ausdruck (expression)
    Aber als Clojure-Entwickler leide ich in der Realität unter der Invasion von Python

    • Ich bin auch Python-Entwickler, verwende Clojure aber nur für private Projekte
      Inzwischen leide ich unter der Invasion von TypeScript, daher kann ich das nachvollziehen
    • Nachdem ich Rust gelernt hatte, wurde mir klar, dass nicht nur rein funktionale Sprachen alles als Ausdruck behandeln können
      Dieser Ansatz ist wirklich nützlich, um den Umfang von Änderungen zu begrenzen
    • Clojure ist immer schneller als Python, das ist immerhin ein Trost
    • Du bist einfach jemand, der Clojure benutzt; du musst dich nicht als „Clojure-Programmierer“ definieren
      Du musst dich nicht in Stammeskriege zwischen Sprachen hineinziehen lassen
      In einer Zeit steigender Produktivität sind solche Grenzen bedeutungslos
      Ich empfehle den Artikel Don’t Call Yourself a Programmer
  • Ich versuche Neuzuweisungen von Variablen zu minimieren, nutze aber oft Variablenschattening
    Ich mag Muster wie result = result.process(), weil sie knapp sind

    • Das liegt wohl am abstrakten Beispiel, aber in den meisten Fällen kann man jedem Schritt einen klaren Namen geben
    • Solche Muster können Sicherheitslücken verursachen
      Wenn process() zum Beispiel eine Validierungsfunktion ist, kann unklar werden, an welchem Punkt der verarbeitete Wert steht
      Deshalb ist es besser, Zustände im Namen klar voneinander zu unterscheiden
    • Im funktionalen Stil lässt sich das ohne solche Zwischenvariablen durch Function Chaining lösen
      Beispiel: result = x |> foo |> bar |> baz oder (-> x foo bar baz)
    • result.process()? Was für ein result und was für ein process soll das sein?“
      Wer den Code später liest, wird verwirrt sein
    • Wenn es schon ein result ist und dann noch einmal process aufgerufen wird, ist das logisch merkwürdig
  • Schon der Begriff „Variable“ fühlt sich für mich seltsam an
    Wenn sie unveränderlich ist, warum nennt man sie dann Variable?

    • Eine Variable ändert sich während der Ausführung nicht, aber ihr Wert kann sich von Aufruf zu Aufruf unterscheiden
      In Rust muss Veränderbarkeit mit mut explizit angegeben werden
      In C muss man dagegen den Präprozessor verwenden, um Konstanten zu erstellen, was verwirrend ist
    • Das Funktionsargument x erhält bei jedem Aufruf einen anderen Wert und ist damit selbst ein variierender Wert
      Auch ohne Neuzuweisung kann man es also Variable nennen
    • Auch in der Mathematik ist eine Variable ein Symbol für einen beliebigen Wert statt für ein konkretes Objekt
      Die Programmierung hat dieses Konzept direkt übernommen
    • Letztlich ist es eine Variable, weil ihr Wert sich von Ausführung zu Ausführung ändern kann
      Eine Konstante hat in allen Ausführungen denselben Wert
      Siehe Variable (mathematics)
    • „Variable“ bedeutet hier nicht, dass sich etwas mit der Zeit ändert, sondern dass es sich je nach Kontext unterscheidet
  • Es wäre gut, wenn die IDE visuell markieren würde, ob eine Variable verändert wurde
    Zum Beispiel durch eine leichte Kennzeichnung geänderter Variablen

    • In IntelliJ werden neu zugewiesene Variablen unterstrichen, und beim Darüberfahren mit der Maus erscheint der Hinweis „Reassigned local variable“
      Wenn man möglichst oft final verwendet, wird Code leichter lesbar und wartbarer
    • Ich halte jedoch ein explizites Opt-in für besser als automatische Inferenz
      Die IDE sollte warnen und Änderungen nur dann zulassen, wenn sie wirklich nötig sind
      Wie in Rich Hickeys Geschichte über set vs list sollte man Strukturen wählen, die Bedeutung klar ausdrücken
    • Swift erkennt per Compiler, ob eine Variable verändert wird, und schlägt bei unnötiger Veränderbarkeit vor, sie in eine Konstante umzuwandeln
    • In JetBrains-IDEs gibt es bereits eine Funktion, um Lese-/Schreibzugriffe auf Variablen zu finden
    • Wenn man dafür einen Linter bauen würde, wäre „mutalator“ ein passender Name
  • Früher habe ich an einem Projekt gearbeitet, das Unveränderlichkeit aus Gründen der Thread-Sicherheit strikt durchgesetzt hat
    Dadurch wurde der Code leichter lesbar, und es war einfacher nachzuvollziehen, was sich ändern kann
    Seitdem bin ich ein glühender Fan von Unveränderlichkeit

    • Dann würde ich dir unbedingt empfehlen, Rust auszuprobieren
  • Nachdem ich in einer großen Haskell-Codebasis gearbeitet hatte und dann wieder zu C zurückkehrte, dachte ich, Unveränderlichkeit sollte der Standard sein
    const reicht dafür nicht aus

    • In C sind eigentlich keine direkten Änderungen möglich, sondern nur Neuzuweisungen von Werten
      Um etwas zu verändern, braucht man Zeiger, und in C++ können schon Funktionsaufrufe Argumente verändern, was undurchsichtig ist
    • Ich frage mich, ob Rusts Standardverhalten ausreichend sichere Unveränderlichkeit bietet
  • Ich stimme der Aussage zu, dass man „fast alle Variablen als const deklarieren“ sollte
    Da sollte Rust erwähnt werden

    • In dem Zusammenhang ist John Carmacks Tweet lesenswert
    • Und auch Zig folgt derselben Philosophie
  • Ich stelle mir eine Syntax vor, bei der Unveränderlichkeit der Standard ist und mutable nur innerhalb eines bestimmten Blocks erlaubt ist
    Zum Beispiel wie ein with-Block in Python

    with mutable(x, items):
        x = 3
        items.append(4)
    

    Sobald man den Block verlässt, wäre wieder alles unveränderlich

    • Das ist im Grunde das Konzept eines mutable borrow
      Am Borrow Checker von Rust sieht man, wie komplex dieses Konzept ist
    • Ohne Borrow-Checks könnten Referenzen außerhalb des Blocks bestehen bleiben und weiterhin Änderungen erlauben
  • Es gibt den Satz: „State is the enemy
    Je mehr Zustand es gibt, desto exponentiell mehr Bedingungen müssen getestet werden
    Unveränderlichkeit ist ein Weg, diese Zustandsexplosion zu verhindern