Suivant: G-Expressions, Précédent: Dérivations, Monter: Interface de programmation [Table des matières][Index]
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 »23. 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.
Évalue n’importe quelle forme >>=
ou return
dans body
comme une monad.
Renvoie une valeur monadique qui encapsule val.
Lie une valeur monadique mval, en passant son « contenu » aux
procédures monadiques mproc…24. 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
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).
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.
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.
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.
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
.
Renvoie l’état actuel dans une valeur monadique.
Initialise l’état actuel à value et renvoie l’état précédent dans une valeur monadique.
Pousse value sur l’état actuel, qui est supposé être une liste, et renvoie l’état précédent dans une valeur monadique.
Récupère (pop) une valeur dans l’état actuel et la renvoie comme une valeur monadique. L’état est supposé être une liste.
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.
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).
Lance mval, une valeur monadique dans la monade du dépôt, dans store, une connexion ouvert au dépôt.
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.
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.
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 :
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.
Version monadique de package-derivation
et
package-cross-derivation
(voir Définition des paquets).
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.
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]