Nächste: G-Ausdrücke, Vorige: Ableitungen, Nach oben: Programmierschnittstelle [Inhalt][Index]
Die auf dem Store arbeitenden Prozeduren, die in den vorigen Abschnitten beschrieben wurden, nehmen alle eine offene Verbindung zum Erstellungs-Daemon als ihr erstes Argument entgegen. Obwohl das ihnen zu Grunde liegende Modell funktional ist, weisen sie doch alle Nebenwirkungen auf oder hängen vom momentanen Zustand des Stores ab.
Ersteres ist umständlich, weil die Verbindung zum Erstellungs-Daemon zwischen all diesen Funktionen durchgereicht werden muss, so dass eine Komposition mit Funktionen ohne diesen Parameter unmöglich wird. Letzteres kann problematisch sein, weil Operationen auf dem Store Nebenwirkungen und/oder Abhängigkeiten von externem Zustand haben und ihre Ausführungsreihenfolge deswegen eine Rolle spielt.
Hier kommt das Modul (guix monads)
ins Spiel. Im Rahmen dieses Moduls
können Monaden benutzt werden und dazu gehört insbesondere eine für
unsere Zwecke sehr nützliche Monade, die Store-Monade. Monaden sind
ein Konstrukt, mit dem zwei Dinge möglich sind: eine Assoziation von Werten
mit einem „Kontext“ (in unserem Fall ist das die Verbindung zum Store) und
das Festlegen einer Reihenfolge für Berechnungen (hiermit sind auch Zugriffe
auf den Store gemeint). Werte in einer Monade – solche, die mit
weiterem Kontext assoziiert sind – werden monadische Werte
genannt; Prozeduren, die solche Werte liefern, heißen monadische
Prozeduren.
Betrachten Sie folgende „normale“ Prozedur:
(define (sh-symlink store)
;; Eine Ableitung liefern, die mit der ausführbaren Datei „bash“
;; symbolisch verknüpft.
(let* ((drv (package-derivation store bash))
(out (derivation->output-path drv))
(sh (string-append out "/bin/bash")))
(build-expression->derivation store "sh"
`(symlink ,sh %output))))
Unter Verwendung von (guix monads)
und (guix gexp)
lässt sie
sich als monadische Funktion aufschreiben:
(define (sh-symlink)
;; Ebenso, liefert aber einen monadischen Wert.
(mlet %store-monad ((drv (package->derivation bash)))
(gexp->derivation "sh"
#~(symlink (string-append #$drv "/bin/bash")
#$output))))
An der zweiten Version lassen sich mehrere Dinge beobachten: Der Parameter
Store
ist jetzt implizit geworden und wurde in die Aufrufe der
monadischen Prozeduren package->derivation
und
gexp->derivation
„eingefädelt“ und der von package->derivation
gelieferte monadische Wert wurde mit mlet
statt einem einfachen
let
gebunden.
Wie sich herausstellt, muss man den Aufruf von package->derivation
nicht einmal aufschreiben, weil er implizit geschieht, wie wir später sehen
werden (siehe G-Ausdrücke):
(define (sh-symlink)
(gexp->derivation "sh"
#~(symlink (string-append #$bash "/bin/bash")
#$output)))
Die monadische sh-symlink
einfach aufzurufen, bewirkt nichts. Wie
jemand einst sagte: „Mit einer Monade geht man um, wie mit Gefangenen, gegen
die man keine Beweise hat: Man muss sie laufen lassen.“ Um also aus der
Monade auszubrechen und die gewünschte Wirkung zu erzielen, muss man
run-with-store
benutzen:
(run-with-store (open-connection) (sh-symlink)) ⇒ /gnu/store/…-sh-symlink
Erwähnenswert ist, dass das Modul (guix monad-repl)
die REPL von
Guile um neue „Befehle“ erweitert, mit denen es leichter ist, mit
monadischen Prozeduren umzugehen: run-in-store
und
enter-store-monad
(siehe Interaktiv mit Guix arbeiten). Mit
Ersterer wird ein einzelner monadischer Wert durch den Store „laufen
gelassen“:
scheme@(guile-user)> ,run-in-store (package->derivation hello) $1 = #<derivation /gnu/store/…-hello-2.9.drv => …>
Mit Letzterer wird rekursiv eine weitere REPL betreten, in der alle Rückgabewerte automatisch durch den Store laufen gelassen werden:
scheme@(guile-user)> ,enter-store-monad store-monad@(guile-user) [1]> (package->derivation hello) $2 = #<derivation /gnu/store/…-hello-2.9.drv => …> store-monad@(guile-user) [1]> (text-file "foo" "Hallo!") $3 = "/gnu/store/…-foo" store-monad@(guile-user) [1]> ,q scheme@(guile-user)>
Beachten Sie, dass in einer store-monad
-REPL keine nicht-monadischen
Werte zurückgeliefert werden können.
Es gibt noch andere Meta-Befehle auf der REPL, so etwa ,build
, womit
ein dateiartiges Objekt erstellt wird (siehe Interaktiv mit Guix arbeiten).
Die wichtigsten syntaktischen Formen, um mit Monaden im Allgemeinen
umzugehen, werden im Modul (guix monads)
bereitgestellt und sind im
Folgenden beschrieben.
Alle >>=
- oder return
-Formen im Rumpf in der
Monade auswerten.
Einen monadischen Wert liefern, der den übergebenen Wert kapselt.
Den monadischen Wert mWert binden, wobei sein „Inhalt“ an die monadischen Prozeduren mProz… übergeben wird22. Es kann eine einzelne mProz oder mehrere davon geben, wie in diesem Beispiel:
(run-with-state (with-monad %state-monad (>>= (return 1) (lambda (x) (return (+ 1 x))) (lambda (x) (return (* 2 x))))) 'irgendein-Zustand) ⇒ 4 ⇒ irgendein-Zustand
Rumpf binden, der eine Folge von Ausdrücken ist. Wie beim
bind-Operator kann man es sich vorstellen als „Auspacken“ des rohen,
nicht-monadischen Werts, der im mWert steckt, wobei anschließend
dieser rohe, nicht-monadische Wert im Sichtbarkeitsbereich des Rumpfs
von der Variablen bezeichnet wird. Die Form (Variable ->
Wert) bindet die Variable an den „normalen“ Wert, wie es
let
tun würde. Die Bindungsoperation geschieht in der Reihenfolge von
links nach rechts. Der letzte Ausdruck des Rumpfs muss ein monadischer
Ausdruck sein und dessen Ergebnis wird das Ergebnis von mlet
oder
mlet*
werden, wenn es durch die Monad laufen gelassen wurde.
mlet*
verhält sich gegenüber mlet
wie let*
gegenüber
let
(siehe Local Bindings in Referenzhandbuch zu GNU
Guile).
Der Reihe nach den mAusdruck und die nachfolgenden monadischen Ausdrücke binden und als Ergebnis das des letzten Ausdrucks liefern. Jeder Ausdruck in der Abfolge muss ein monadischer Ausdruck sein.
Dies verhält sich ähnlich wie mlet
, außer dass die Rückgabewerte der
monadischen Prozeduren ignoriert werden. In diesem Sinn verhält es sich
analog zu begin
, nur auf monadischen Ausdrücken.
Wenn die Bedingung wahr ist, wird die Folge monadischer Ausdrücke
mAusdr0..mAusdr* wie bei mbegin
ausgewertet. Wenn die
Bedingung falsch ist, wird *unspecified*
(„unbestimmt“) in der
momentanen Monade zurückgeliefert. Jeder Ausdruck in der Folge muss ein
monadischer Ausdruck sein.
Wenn die Bedingung falsch ist, wird die Folge monadischer Ausdrücke
mAusdr0..mAusdr* wie bei mbegin
ausgewertet. Wenn die
Bedingung wahr ist, wird *unspecified*
(„unbestimmt“) in der
momentanen Monade zurückgeliefert. Jeder Ausdruck in der Folge muss ein
monadischer Ausdruck sein.
Das Modul (guix monads)
macht die Zustandsmonade (englisch
„state monad“) verfügbar, mit der ein zusätzlicher Wert – der
Zustand – durch die monadischen Prozeduraufrufe gefädelt werden
kann.
Die Zustandsmonade. Prozeduren in der Zustandsmonade können auf den gefädelten Zustand zugreifen und ihn verändern.
Betrachten Sie das folgende Beispiel. Die Prozedur Quadrat
liefert
einen Wert in der Zustandsmonade zurück. Sie liefert das Quadrat ihres
Arguments, aber sie inkrementiert auch den momentanen Zustandswert:
(define (Quadrat x) (mlet %state-monad ((Anzahl (current-state))) (mbegin %state-monad (set-current-state (+ 1 Anzahl)) (return (* x x))))) (run-with-state (sequence %state-monad (map Quadrat (iota 3))) 0) ⇒ (0 1 4) ⇒ 3
Wird das „durch“ die Zustandsmonade %state-monad
laufen gelassen,
erhalten wir jenen zusätzlichen Zustandswert, der der Anzahl der Aufrufe von
Quadrat
entspricht.
Liefert den momentanen Zustand als einen monadischen Wert.
Setzt den momentanen Zustand auf Wert und liefert den vorherigen Zustand als einen monadischen Wert.
Hängt den Wert vorne an den momentanen Zustand an, der eine Liste sein muss. Liefert den vorherigen Zustand als monadischen Wert.
Entfernt einen Wert vorne vom momentanen Zustand und liefert ihn als monadischen Wert zurück. Dabei wird angenommen, dass es sich beim Zustand um eine Liste handelt.
Den monadischen Wert mWert mit Zustand als initialem Zustand laufen lassen. Dies liefert zwei Werte: den Ergebniswert und den Ergebniszustand.
Die zentrale Schnittstelle zur Store-Monade, wie sie vom Modul (guix
store)
angeboten wird, ist die Folgende:
Die Store-Monade – ein anderer Name für %state-monad
.
Werte in der Store-Monade kapseln Zugriffe auf den Store. Sobald ihre
Wirkung gebraucht wird, muss ein Wert der Store-Monade „ausgewertet“ werden,
indem er an die Prozedur run-with-store
übergeben wird (siehe unten).
Den mWert, einen monadischen Wert in der Store-Monade, in der offenen Verbindung Store laufen lassen.
Als monadischen Wert den absoluten Dateinamen im Store für eine Datei liefern, deren Inhalt der der Zeichenkette Text ist. Referenzen ist dabei eine Liste von Store-Objekten, die die Ergebnis-Textdatei referenzieren wird; der Vorgabewert ist die leere Liste.
Den absoluten Dateinamen im Store als monadischen Wert für eine Datei liefern, deren Inhalt der des Byte-Vektors Daten ist. Referenzen ist dabei eine Liste von Store-Objekten, die die Ergebnis-Binärdatei referenzieren wird; der Vorgabewert ist die leere Liste.
nachdem sie in den Store interniert wurde. Dabei wird der Name als ihr Store-Name verwendet, oder, wenn kein Name angegeben wurde, der Basisname der Datei.
Ist recursive? wahr, werden in der Datei enthaltene Dateien rekursiv hinzugefügt; ist die Datei eine flache Datei und recursive? ist wahr, wird ihr Inhalt in den Store eingelagert und ihre Berechtigungs-Bits übernommen.
Steht recursive? auf wahr, wird (select? Datei
Stat)
für jeden Verzeichniseintrag aufgerufen, wobei Datei der
absolute Dateiname und Stat das Ergebnis von lstat
ist, außer
auf den Einträgen, wo select? keinen wahren Wert liefert.
Folgendes Beispiel fügt eine Datei unter zwei verschiedenen Namen in den Store ein:
(run-with-store (open-connection) (mlet %store-monad ((a (interned-file "README")) (b (interned-file "README" "LEGU-MIN"))) (return (list a b)))) ⇒ ("/gnu/store/rwm…-README" "/gnu/store/44i…-LEGU-MIN")
Das Modul (guix packages)
exportiert die folgenden paketbezogenen
monadischen Prozeduren:
monadischen Wert den absoluten Dateinamen der Datei innerhalb des Ausgabeverzeichnisses output des Pakets. Wird keine Datei angegeben, wird der Name des Ausgabeverzeichnisses output für das Paket zurückgeliefert. Ist target wahr, wird sein Wert als das Zielsystem bezeichnendes Tripel zum Cross-Kompilieren benutzt.
Beachten Sie, dass durch diese Prozedur das Paket nicht erstellt wird, also muss ihr Ergebnis keine bereits existierende Datei bezeichnen, kann aber. Wir empfehlen, diese Prozedur nur dann zu benutzen, wenn Sie wissen, was Sie tun.
package-derivation
und package-cross-derivation
(siehe Pakete definieren).
Diese Operation wird gemeinhin „bind“ genannt, aber mit diesem Begriff wird in Guile eine völlig andere Prozedur bezeichnet, die nichts damit zu tun hat. Also benutzen wir dieses etwas kryptische Symbol als Erbe der Haskell-Programmiersprache.
Nächste: G-Ausdrücke, Vorige: Ableitungen, Nach oben: Programmierschnittstelle [Inhalt][Index]