Next: , Previous: , Up: 编程接口   [Contents][Index]


9.11 仓库monad

The procedures that operate on the store described in the previous sections all take an open connection to the build daemon as their first argument. Although the underlying model is functional, they either have side effects or depend on the current state of the store.

The former is inconvenient: the connection to the build daemon has to be carried around in all those functions, making it impossible to compose functions that do not take that parameter with functions that do. The latter can be problematic: since store operations have side effects and/or depend on external state, they have to be properly sequenced.

This is where the (guix monads) module comes in. This module provides a framework for working with monads, and a particularly useful monad for our uses, the store monad. Monads are a construct that allows two things: associating “context” with values (in our case, the context is the store), and building sequences of computations (here computations include accesses to the store). Values in a monad—values that carry this additional context—are called monadic values; procedures that return such values are called monadic procedures.

Consider this “normal” procedure:

(define (sh-symlink store)
  ;; Return a derivation that symlinks the 'bash' executable.
  (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))))

Using (guix monads) and (guix gexp), it may be rewritten as a monadic function:

(define (sh-symlink)
  ;; Same, but return a monadic value.
  (mlet %store-monad ((drv (package->derivation bash)))
    (gexp->derivation "sh"
                      #~(symlink (string-append #$drv "/bin/bash")
                                 #$output))))

There are several things to note in the second version: the store parameter is now implicit and is “threaded” in the calls to the package->derivation and gexp->derivation monadic procedures, and the monadic value returned by package->derivation is bound using mlet instead of plain let.

As it turns out, the call to package->derivation can even be omitted since it will take place implicitly, as we will see later (see G-表达式):

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

Calling the monadic sh-symlink has no effect. As someone once said, “you exit a monad like you exit a building on fire: by running”. So, to exit the monad and get the desired effect, one must use run-with-store:

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

Note that the (guix monad-repl) module extends the Guile REPL with new “commands” to make it easier to deal with monadic procedures: run-in-store, and enter-store-monad (see Using Guix Interactively). The former is used to “run” a single monadic value through the store:

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

The latter enters a recursive REPL, where all the return values are automatically run through the store:

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

Note that non-monadic values cannot be returned in the store-monad REPL.

Other meta-commands are available at the REPL, such as ,build to build a file-like object (see Using Guix Interactively).

The main syntactic forms to deal with monads in general are provided by the (guix monads) module and are described below.

Scheme Syntax: with-monad monad body ...

Evaluate any >>= or return forms in body as being in monad.

Scheme Syntax: return val

Return a monadic value that encapsulates val.

Scheme Syntax: >>= mval mproc ...

Bind monadic value mval, passing its “contents” to monadic procedures mproc22. There can be one mproc or several of them, as in this example:

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

 4
 some-state
Scheme Syntax: mlet monad ((var mval) ...) body ...
Scheme Syntax: mlet* monad ((var mval) ...) body ... Bind the variables var to the monadic values

mval in body, which is a sequence of expressions. As with the bind operator, this can be thought of as “unpacking” the raw, non-monadic value “contained” in mval and making var refer to that raw, non-monadic value within the scope of the body. The form (var -> val) binds var to the “normal” value val, as per let. The binding operations occur in sequence from left to right. The last expression of body must be a monadic expression, and its result will become the result of the mlet or mlet* when run in the monad.

mlet* is to mlet what let* is to let (see Local Bindings in GNU Guile Reference Manual).

Scheme System: mbegin monad mexp ...

Bind mexp and the following monadic expressions in sequence, returning the result of the last expression. Every expression in the sequence must be a monadic expression.

This is akin to mlet, except that the return values of the monadic expressions are ignored. In that sense, it is analogous to begin, but applied to monadic expressions.

Scheme System: mwhen condition mexp0 mexp* ...

When condition is true, evaluate the sequence of monadic expressions mexp0..mexp* as in an mbegin. When condition is false, return *unspecified* in the current monad. Every expression in the sequence must be a monadic expression.

Scheme System: munless condition mexp0 mexp* ...

When condition is false, evaluate the sequence of monadic expressions mexp0..mexp* as in an mbegin. When condition is true, return *unspecified* in the current monad. Every expression in the sequence must be a monadic expression.

The (guix monads) module provides the state monad, which allows an additional value—the state—to be threaded through monadic procedure calls.

Scheme Variable: %state-monad

The state monad. Procedures in the state monad can access and change the state that is threaded.

Consider the example below. The square procedure returns a value in the state monad. It returns the square of its argument, but also increments the current state value:

(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

When “run” through %state-monad, we obtain that additional state value, which is the number of square calls.

Monadic Procedure: current-state

Return the current state as a monadic value.

Monadic Procedure: set-current-state value

Set the current state to value and return the previous state as a monadic value.

Monadic Procedure: state-push value

Push value to the current state, which is assumed to be a list, and return the previous state as a monadic value.

Monadic Procedure: state-pop

Pop a value from the current state and return it as a monadic value. The state is assumed to be a list.

Scheme Procedure: run-with-state mval [state]

Run monadic value mval starting with state as the initial state. Return two values: the resulting value, and the resulting state.

The main interface to the store monad, provided by the (guix store) module, is as follows.

Scheme Variable: %store-monad

The store monad—an alias for %state-monad.

Values in the store monad encapsulate accesses to the store. When its effect is needed, a value of the store monad must be “evaluated” by passing it to the run-with-store procedure (see below).

Scheme Procedure: run-with-store store mval [#:guile-for-build] [#:system (%current-system)]

Run mval, a monadic value in the store monad, in store, an open store connection.

Monadic Procedure: text-file name text [references]

Return as a monadic value the absolute file name in the store of the file containing text, a string. references is a list of store items that the resulting text file refers to; it defaults to the empty list.

Monadic Procedure: binary-file name data [references]

Return as a monadic value the absolute file name in the store of the file containing data, a bytevector. references is a list of store items that the resulting binary file refers to; it defaults to the empty list.

Monadic Procedure: interned-file file [name] [#:recursive? #t] [#:select? (const #t)] Return the name of file once

interned in the store. Use name as its store name, or the basename of file if name is omitted.

When recursive? is true, the contents of file are added recursively; if file designates a flat file and recursive? is true, its contents are added, and its permission bits are kept.

When recursive? is true, call (select? file stat) for each directory entry, where file is the entry’s absolute file name and stat is the result of lstat; exclude entries for which select? does not return true.

The example below adds a file to the store, under two different names:

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

The (guix packages) module exports the following package-related monadic procedures:

Monadic Procedure: package-file package [file] [#:system (%current-system)] [#:target #f]  [#:output "out"] Return as a

monadic value in the absolute file name of file within the output directory of package. When file is omitted, return the name of the output directory of package. When target is true, use it as a cross-compilation target triplet.

Note that this procedure does not build package. Thus, the result might or might not designate an existing file. We recommend not using this procedure unless you know what you are doing.

Monadic Procedure: package->derivation package [system]
Monadic Procedure: package->cross-derivation package target [system] Monadic version of package-derivation and

package-cross-derivation (see 定义软件包).


Footnotes

(22)

This operation is commonly referred to as “bind”, but that name denotes an unrelated procedure in Guile. Thus we use this somewhat cryptic symbol inherited from the Haskell language.


Next: G-表达式, Previous: Derivations, Up: 编程接口   [Contents][Index]