Nächste: , Vorige: , Nach oben: Programmierschnittstelle   [Inhalt][Index]


6.6 Die Store-Monade

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 „Meta-Befehle“ erweitert, mit denen es leichter ist, mit monadischen Prozeduren umzugehen: run-in-store und enter-store-monad. 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.

Die wichtigsten syntaktischen Formen, um mit Monaden im Allgemeinen umzugehen, werden im Modul (guix monads) bereitgestellt und sind im Folgenden beschrieben.

Scheme-Syntax: with-monad Monade Rumpf ...

Alle >>=- oder return-Formen im Rumpf in der Monade auswerten.

Scheme-Syntax: return Wert

Einen monadischen Wert liefern, der den übergebenen Wert kapselt.

Scheme-Syntax: >>= mWert mProz ...

Den monadischen Wert mWert binden, wobei sein „Inhalt“ an die monadischen Prozeduren mProz… übergeben wird16. 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
Scheme-Syntax: mlet Monade ((Variable mWert) ...) Rumpf ...
Scheme-Syntax: mlet* Monade ((Variable mWert) ...) Rumpf ... Die Variablen an die monadischen Werte mWert im

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 GNU Guile Reference Manual).

Scheme-System: mbegin Monade mAusdruck ...

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.

Scheme-System: mwhen Bedingung mAusdr0 mAusdr* ...

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.

Scheme-System: munless Bedingung mAusdr0 mAusdr* ...

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.

Scheme-Variable: %state-monad

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.

Monadische Prozedur: current-state

Liefert den momentanen Zustand als einen monadischen Wert.

Monadische Prozedur: set-current-state Wert

Setzt den momentanen Zustand auf Wert und liefert den vorherigen Zustand als einen monadischen Wert.

Monadische Prozedur: state-push Wert

Hängt den Wert vorne an den momentanen Zustand an, der eine Liste sein muss. Liefert den vorherigen Zustand als monadischen Wert.

Monadische Prozedur: state-pop

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.

Scheme-Prozedur: run-with-state mWert [Zustand]

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:

Scheme-Variable: %store-monad

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).

Scheme-Prozedur: run-with-store Store mWert [#:guile-for-build] [#:system (%current-system)]

Den mWert, einen monadischen Wert in der Store-Monade, in der offenen Verbindung Store laufen lassen.

Monadische Prozedur: text-file Name Text [Referenzen]

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.

Monadische Prozedur: binary-file Name Daten [Referenzen]

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.

Monadische Prozedur: interned-file Datei [Name] [#:recursive? #t] [#:select? (const #t)] Liefert den Namen der Datei,

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:

Monadische Prozedur: package-file Paket [Datei] [#:system (%current-system)] [#:target #f]  [#:output "out"] Liefert als

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.

Monadische Prozedur: package->derivation Paket [System]
Monadische Prozedur: package->cross-derivation Paket Ziel [System] Monadische Version von package-derivation

und package-cross-derivation (siehe Pakete definieren).


Fußnoten

(16)

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: , Vorige: , Nach oben: Programmierschnittstelle   [Inhalt][Index]