Suivant: , Précédent: , Monter: Interface de programmation   [Table des matières][Index]


8.11 La monade du dépôt

Les procédures qui travaillent sur le dépôt décrites dans les sections précédentes prennent toutes une connexion ouverte au démon de construction comme premier argument. Bien que le modèle sous-jacent soit fonctionnel, elles ont soit des effets de bord, soit dépendent de l’état actuel du dépôt.

Le premier point est embêtant : on doit se balader avec la connexion au démon dans toutes ces fonctions, ce qui rend impossible le fait de composer des fonctions qui ne prennent pas ce paramètre avec des fonctions qui le prennent. Le deuxième point est problématique : comme les opérations sur le dépôt ont des effets de bord ou dépendent d’états externes, elles doivent être enchaînés correctement.

C’est là que le module (guix monads) arrive à la rescousse. Ce module fournit un cadre pour travailler avec des monads, en particulier une monade très utile pour notre usage, la monade du dépôt. Les monades sont des constructions qui permettent deux choses : associer un « contexte » avec une valeur (dans notre cas, le contexte est le dépôt) et construire une séquence de calculs (ici les calculs comprennent des accès au dépôt). Les valeurs dans une monade — les valeurs qui contiennent ce contexte supplémentaire — sont appelées des valeurs monadiques ; les procédures qui renvoient ce genre de valeur sont appelées des procédures monadiques.

Considérez cette procédure « normale » :

(define (sh-symlink store)
  ;; Renvoie une dérivation qui crée un lien symbolique vers l'exécutable « bash ».
  (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))))

En utilisant (guix monads) et (guix gexp), on peut la réécrire en une fonction monadique :

(define (sh-symlink)
  ;; Pareil, mais renvoie une valeur monadique.
  (mlet %store-monad ((drv (package->derivation bash)))
    (gexp->derivation "sh"
                      #~(symlink (string-append #$drv "/bin/bash")
                                 #$output))))

Il y a plusieurs choses à remarquer avec cette deuxième version : le paramètre store est maintenant implicitement « enfilé » dans les appels aux procédures monadiques package->derivation et gexp->derivation, et la valeur monadique renvoyée par package->derivation est liée avec mlet plutôt qu’avec un simple let.

Il se trouve que l’appel à package->derivation peut même être omis puisqu’il aura lieu implicitement, comme nous le verrons plus tard (voir G-Expressions) :

(define (sh-symlink)
  (gexp->derivation "sh"
                    #~(symlink (string-append #$bash "/bin/bash")
                               #$output)))

L’appel à la procédure monadique sh-symlink n’a aucun effet. Comme on pourrait le dire, « on sort d’une monade comme de la monarchie : en l’exécutant »22. Donc, pour sortir de la monade et obtenir l’effet escompté, on doit utiliser run-with-store :

(run-with-store (open-connection) (sh-symlink))
 /gnu/store/...-sh-symlink

Remarquez que le module (guix monad-repl) étend la console Guile avec de nouvelles « commandes » pour rendre plus facile la manipulation de procédures monadiques : run-in-store et enter-store-monad (voir Utiliser Guix de manière interactive). La première est utilisée pour « lancer » une seule valeur monadique à travers le dépôt :

scheme@(guile-user)> ,run-in-store (package->derivation hello)
$1 = #<derivation /gnu/store/…-hello-2.9.drv => …>

La deuxième entre dans une console récursive, où toutes les valeurs de retour sont automatiquement lancées à travers le dépôt :

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 "toto" "Hello!")
$3 = "/gnu/store/…-toto"
store-monad@(guile-user) [1]> ,q
scheme@(guile-user)>

Remarquez qu’on ne peut pas renvoyer de valeur non monadique dans la console store-monad.

D’autres méta-commandes sont disponibles sur la REPL, comme ,build pour construire un objet simili-fichier (voir Utiliser Guix de manière interactive).

Les formes syntaxiques principales pour utiliser des monades en général sont disponibles dans le module (guix monads) et sont décrites ci-dessous.

Macro :with-monad monad body …

Évalue n’importe quelle forme >>= ou return dans body comme une monad.

Macro :return val

Renvoie une valeur monadique qui encapsule val.

Macro :>>= mval mproc …

Lie une valeur monadique mval, en passant son « contenu » aux procédures monadiques mproc23. Il peut y avoir une ou plusieurs mproc, comme dans cet exemple :

(run-with-state
    (with-monad %state-monad
      (>>= (return 1)
           (lambda (x) (return (+ 1 x)))
           (lambda (x) (return (* 2 x)))))
  'some-state)

 4
 some-state
Macro :mlet monad ((var mval) …) body …
Macro :mlet* monad ((var mval) …) body …

Lie les variables var aux valeurs monadiques mval dans body, une séquence d’expressions. Comme avec l’opérateur de liaison, on peut réfléchir comme si on « ouvrait » la valeur non-monadique « contenue » dans mval et comme si on faisait en sorte que var se réfère à cette valeur pure, non-monadique, dans la portée de body. La forme (var -> val) lie var à la valeur « normale » val, comme let. L’opération de liaison a lieu en séquence de la gauche vers la droite. La dernière expression de body doit être une expression monadique et son résultat deviendra le résultat de mlet ou mlet* lorsque lancé dans la monad.

mlet* est à mlet ce que let* est à let (voir Local Bindings dans GNU Guile Reference Manual).

Macro :mbegin monad mexp …

Lie mexp et les expressions monadiques suivantes en séquence, et renvoie le résultat de la dernière expression. Chaque expression dans la séquence doit être une expression monadique.

Cette procédure est similaire à mlet, sauf que les valeurs de retour des expressions monadiques sont ignorées. Dans ce sens, elle est analogue à begin, mais appliqué à des expressions monadiques.

Macro :mwhen condition mexp0 mexp* …

Lorsque la condition est vraie, évalue la séquence des expressions monadiques mexp0..mexp* comme dans un mbegin. Lorsque la condition est fausse, renvoie *unspecified* dans la monade actuelle. Chaque expression dans la séquence doit être une expression monadique.

Macro :munless condition mexp0 mexp* …

Lorsque la condition est fausse, évalue la séquence des expressions monadiques mexp0..mexp* comme dans un mbegin. Lorsque la condition est vraie, renvoie *unspecified* dans la monade actuelle. Chaque expression dans la séquence doit être une expression monadique.

Le module (guix monads) fournit la monade d’état qui permet à une valeur supplémentaire — l’état — d’être enfilée à travers les appels de procédures.

Variable :%state-monad

La monade d’état. les procédure dans la monade d’état peuvent accéder et modifier l’état qui est enfilé.

Considérez l’exemple ci-dessous. La procédure square renvoie une valeur dans la monade d’état. Elle renvoie le carré de son argument, mais incrémente aussi la valeur actuelle de l’état :

(define (square x)
  (mlet %state-monad ((count (current-state)))
    (mbegin %state-monad
      (set-current-state (+ 1 count))
      (return (* x x)))))

(run-with-state (sequence %state-monad (map square (iota 3))) 0)
 (0 1 4)
 3

Lorsque c’est « lancé » à travers %state-monad, nous obtenons cette valeur d’état supplémentaire, qui est le nombre d’appels au square.

Procédure monadique :current-state

Renvoie l’état actuel dans une valeur monadique.

Procédure monadique :set-current-state value

Initialise l’état actuel à value et renvoie l’état précédent dans une valeur monadique.

Procédure monadique :state-push value

Pousse value sur l’état actuel, qui est supposé être une liste, et renvoie l’état précédent dans une valeur monadique.

Procédure monadique :state-pop

Récupère (pop) une valeur dans l’état actuel et la renvoie comme une valeur monadique. L’état est supposé être une liste.

Procédure :run-with-state mval [state]

Lance la valeur monadique mval avec state comme valeur initiale. Renvoie deux valeurs : la valeur du résultat et l’état du résultat.

L’interface principale avec la monade du dépôt, fournit par le module (guix store), est la suivante.

Variable :%store-monad

La monade du dépôt — un alias pour %state-monad.

Les valeurs dans la monade du dépôt encapsulent les accès vers le dépôt. Lorsque son effet est nécessaire, une valeur de la monade du dépôt doit être « évaluée » en la passant à la procédure run-with-store (voir ci-dessous).

Procédure :run-with-store store mval [#:guile-for-build] [#:system (%current-system)]

Lance mval, une valeur monadique dans la monade du dépôt, dans store, une connexion ouvert au dépôt.

Procédure monadique :text-file name text [references]

Renvoie une valeur monadique correspondant au nom de fichier dans le dépôt du fichier contenant text, une chaîne de caractères. references est une liste d’éléments du dépôt auxquels le fichier texte en résultat se réfère ; c’est la liste vide par défaut.

Procédure monadique :binary-file name data [references]

Renvoie une valeur monadique correspondant au nom de fichier absolu dans le dépôt du fichier contenant data, un vecteur d’octets. references est une liste d’éléments du dépôt auxquels le fichier binaire en résultat se réfère ; c’est la liste vide par défaut.

Procédure monadique :interned-file file [name] [#:recursive? #t] [#:select? (const #t)]

Renvoie le nom de file une fois ajouté au dépôt. Utilise name comme nom dans le dépôt ou le nom de fichier de file si name est omis.

Lorsque recursive? est vraie, le contenu de file est ajouté récursivement ; si file désigne un fichier simple et que recursive? est vrai, son contenu est ajouté et ses bits de permissions sont préservés.

Lorsque recursive? est vraie, appelle (select? file stat) pour chaque répertoire où file est le nom de fichier absolu de l’entrée et stat est le résultat de lstat ; à l’exception des entrées pour lesquelles select? ne renvoie pas vrai.

L’exemple ci-dessous ajoute un fichier au dépôt, sous deux noms différents :

(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")

Le module (guix packages) exporte les procédures monadiques liées aux paquets suivantes :

Procédure monadique :package-file package [file] [#:system (%current-system)] [#:target #f] [#:output "out"]

Renvoie une valeur monadique qui contient le nom de fichier absolu de file dans le répertoire output de package. Lorsque file est omis, renvoie le nom du répertoire output de package. Lorsque target est vrai, l’utilise comme un triplet de cible pour la compilation croisée.

Notez que cette procédure ne permet pas de construire package. Ainsi, le résultat peut ou non désigner un fichier existant. Nous vous recommandons de ne pas utiliser cette procédure si vous ne savez pas ce que vous faites.

Procédure monadique :package->derivation package [system]
Procédure monadique :package->cross-derivation package target [system]

Version monadique de package-derivation et package-cross-derivation (voir Définition des paquets).


Notes de bas de page

(22)

NdT : il y a là un jeu de mot en anglais qui se base sur un double sens de « run », qui peut se traduire par « exécuter » dans ce contexte.

(23)

Cette opération est souvent appelée « bind », mais ce nom dénote une procédure qui n’a rien à voir en Guile. Ainsi, nous empruntons ce symbole quelque peu cryptique au langage Haskell


Suivant: G-Expressions, Précédent: Dérivations, Monter: Interface de programmation   [Table des matières][Index]