with builtins; [ # Kommentare #========================================= # Inline Kommentare sehen so aus. /* Multizeilen Kommentare sehen so aus. */ # Booleans #========================================= (true && false) # Und #=> false (true || false) # Oder #=> true (if 3 < 4 then "a" else "b") # Bedingungen #=> "a" # Integers #========================================= # Integers sind die einzigen numerischen Typen. 1 0 42 (-3) # Einige integers (4 + 6 + 12 - 2) # Addition #=> 20 (7 / 2) # Division #=> 3 # Strings #========================================= "String Literale sind in Anführungszeichen." " String Literale können mehrere Zeilen umspannen. " '' Dies wird als Literal mit eingerückten String bezeichnet. Es entfernt intelligent führende Leerzeichen. '' '' a b '' #=> "a\n b" ("ab" + "cd") # String Konkatenation #=> "abcd" # Mit Antiquotation kannst du Werte in Strings einbetten. ("Dein Homeverzeichnis ist ${getEnv "HOME"}") #=> "Dein Homeverzeichnis ist /home/alice" # Paths #========================================= # Nix besitzt einen primitiven Datentyp für Pfade /tmp/tutorials/learn.nix # Ein relativer Pfad wird beim Parsing zu einem absoluten Pfad aufgelöst, # relativ zu der Datei in der es auftritt. tutorials/learn.nix #=> /the-base-path/tutorials/learn.nix # Ein Pfad muss mindestens einen Schrägstrich enthalten. Ein Pfad für eine # Datei im selben Verzeichnis benötigt ein ./ Präfix. ./learn.nix #=> /the-base-path/learn.nix # Der / Operator muss von Leerraum umgeben sein wenn du dividieren möchtest. 7/2 # Das ist ein Pfadliteral (7 / 2) # Das ist ein Integerliteral # Importe #========================================= # Eine nix Datei besitzt einen einzelnen top-level Ausdruck mit keinen freien Variablen. # Ein Import-Ausdruck wird zum Wert der Datei, die importiert wird, ausgewertet. (import /tmp/foo.nix) # Importe können ebenso mit Strings spezifiziert werden. (import "/tmp/foo.nix") # Import Pfade müssen absolut sein. Pfadliterale # sind automatisch aufgelöst, das ist ein Ordnung. (import ./foo.nix) # Jedoch passiert dies nicht mit Strings. (import "./foo.nix") #=> error: string ‘foo.nix’ doesn't represent an absolute path # Let #========================================= # `let` Blöcke erlauben es uns Werte zu Variablen zu binden. (let x = "a"; in x + x + x) #=> "aaa" # Bindungen können auf sich gegenseitig verweisen. Die Reihenfolge spielt # keine Rolle. (let y = x + "b"; x = "a"; in y + "c") #=> "abc" # Innere Bindungen überschatten Äußere. (let a = 1; in let a = 2; in a) #=> 2 # Funktionen #========================================= (n: n + 1) # Funktion, die 1 addiert ((n: n + 1) 5) # Dieselbe Funktion angewendet auf 5. #=> 6 # Es gibt keine spezielle Syntax für benannte Funktionen, aber sie # können mit `let` Blöcken, wie jeder andere Wert auch, gebunden werden. (let succ = (n: n + 1); in succ 5) #=> 6 # Eine Funktion hat genau ein Argument. # Mehrere Argumente können erreicht werden mithilfe von Currying. ((x: y: x + "-" + y) "a" "b") #=> "a-b" # Benannte Funktionsargumente gibt es auch. Diese werden wir einführen, nachdem wir uns Sets # angeschaut haben. # Listen #========================================= # Listen werden durch eckige Klammern gekennzeichnet. (length [1 2 3 "x"]) #=> 4 ([1 2 3] ++ [4 5]) #=> [1 2 3 4 5] (concatLists [[1 2] [3 4] [5]]) #=> [1 2 3 4 5] (head [1 2 3]) #=> 1 (tail [1 2 3]) #=> [2 3] (elemAt ["a" "b" "c" "d"] 2) #=> "c" (elem 2 [1 2 3]) #=> true (elem 5 [1 2 3]) #=> false (filter (n: n < 3) [1 2 3 4]) #=> [ 1 2 ] # Sets #========================================= # Ein "Set" ist eine ungeordnete Zuordnung mit Stringschlüsseln. { foo = [1 2]; bar = "x"; } # Der . Operator nimmt einen Wert aus dem Set. { a = 1; b = 2; }.a #=> 1 # Der ? Operator testet, ob der Schlüssel in dem Set vorhanden ist. ({ a = 1; b = 2; } ? a) #=> true ({ a = 1; b = 2; } ? c) #=> false # Der // Operator mergt zwei Sets. ({ a = 1; } // { b = 2; }) #=> { a = 1; b = 2; } # Werte auf der rechten Seite überschreiben die Werte auf der linken Seite. ({ a = 1; b = 2; } // { a = 3; c = 4; }) #=> { a = 3; b = 2; c = 4; } # Das Schlüsselwort rec bezeichenet ein "rekursives Set", in dem sich Attribute # aufeinander beziehen können. (let a = 1; in { a = 2; b = a; }.b) #=> 1 (let a = 1; in rec { a = 2; b = a; }.b) #=> 2 # Verschachtelte Sets können stückweise definiert werden. { a.b = 1; a.c.d = 2; a.c.e = 3; }.a.c #=> { d = 2; e = 3; } # Die Nachkommen eines Attributs können in diesem Feld nicht zugeordnet werden, wenn # das Attribut selbst nicht zugewiesen wurde. { a = { b = 1; }; a.c = 2; } #=> error: attribute ‘a’ already defined # With #========================================= # Der Körper eines Sets Blocks wird mit der Zuordnung eines Satzes an die Variablen gebunden. (with { a = 1; b = 2; }; a + b) # => 3 # Innere Bindungen überschatten äußere Bindungen. (with { a = 1; b = 2; }; (with { a = 5; }; a + b)) #=> 7 # Die erste Linie diese Tutorials startet mit "with builtins;", # weil builtins ein Set mit allen eingebauten # Funktionen (length, head, tail, filter, etc.) umfasst. # Das erspart uns beispielsweise "builtins.length" zu schreiben, # anstatt nur "length". # Set patterns #========================================= # Sets sind nützlich, wenn du mehrere Werte einer Funktion # übergeben musst. (args: args.x + "-" + args.y) { x = "a"; y = "b"; } #=> "a-b" # Dies kann mit Hilfe von Set patterns deutlicher geschrieben werden. ({x, y}: x + "-" + y) { x = "a"; y = "b"; } #=> "a-b" # Standardmäßig schlägt das Muster bei Sets mit zusätzlichen Schlüsseln fehl. ({x, y}: x + "-" + y) { x = "a"; y = "b"; z = "c"; } #=> error: anonymous function called with unexpected argument ‘z’ # Durch Hinzufügen von ", ..." können zusätzliche Schlüssel ignoriert werden. ({x, y, ...}: x + "-" + y) { x = "a"; y = "b"; z = "c"; } #=> "a-b" # Errors #========================================= # `throw` bewirkt, dass die Auswertung mit einer Fehlermeldung abgebrochen wird. (2 + (throw "foo")) #=> error: foo # `tryEval` fängt geworfene Fehler. (tryEval 42) #=> { success = true; value = 42; } (tryEval (2 + (throw "foo"))) #=> { success = false; value = false; } # `abort` ist ähnlich wie throw, aber es ist fatal. Es kann nicht gefangen werden. (tryEval (abort "foo")) #=> error: evaluation aborted with the following error message: ‘foo’ # `assert` evaluiert zu dem gegebenen Wert, wenn die Bedingung wahr ist, sonst # löst es eine abfangbare Exception aus. (assert 1 < 2; 42) #=> 42 (assert 1 > 2; 42) #=> error: assertion failed at (string):1:1 (tryEval (assert 1 > 2; 42)) #=> { success = false; value = false; } # Impurity #========================================= # Da die Wiederholbarkeit von Builds für den Nix Packetmanager entscheidend ist, # werden in der Nix Sprache reine funktionale Elemente betont. Es gibt aber ein paar # unreine Elemente. # Du kannst auf Umgebungsvariablen verweisen. (getEnv "HOME") #=> "/home/alice" # Die trace Funktion wird zum Debugging verwendet. Sie gibt das erste Argument zu stderr aus # und evaluiert das zweite Argument. (trace 1 2) #=> trace: 1 #=> 2 # Du kannst Dateien in den Nix Store schreiben. Obwohl unrein, kannst du dir relativ sicher sein, # dass es sicher ist, da der Dateiname aus dem Hash des Inhalts abgeleitet wird. # Du kannst Dateien von überall lesen. In diesem Beispiel schreiben wir Dateien in den Store # und lesen wieder davon. (let filename = toFile "foo.txt" "hello!"; in [filename (builtins.readFile filename)]) #=> [ "/nix/store/ayh05aay2anx135prqp0cy34h891247x-foo.txt" "hello!" ] # Außerdem können wir Dateien in den Nix Store herunterladen. (fetchurl "https://example.com/package-1.2.3.tgz") #=> "/nix/store/2drvlh8r57f19s9il42zg89rdr33m2rm-package-1.2.3.tgz" ]