Next: , Previous: , Up: Программный интерфейс   [Contents][Index]


8.10 Устройство склада

Все процедуры, которые работают с store, описанные в предыдущих разделах, принимают открытое соединение с демоном сборки в качестве первого аргумента. Хотя основная модель является функциональной, они либо имеют побочные эффекты, либо зависят от текущего состояния store.

Первое неудобно: соединение с демоном сборки должно поддерживаться во всех этих функциях, что делает невозможным составление функций, которые не принимают этот параметр, с функциями, которые его принимают. Последнее может быть проблематичным: поскольку операции store имеют побочные эффекты и/или зависят от внешнего состояния, они должны быть правильно упорядочены.

Здесь на помощь приходит модуль (guix monads). Этот модуль предоставляет основу для работы с monads и особенно полезную монаду для наших целей - store monad. Монады - это конструкции, которая позволяют две вещи: связывать “контекст” со значениями (в нашем случае контекст - это store) и строить последовательности вычислений (здесь вычисления включают доступ к store). Значения в монаде—значения, которые несут этот дополнительный контекст—называются монадическими значениями; процедуры, возвращающие такие значения, называются монадическими процедурами.

Рассмотрим эту “нормальную” процедуру:

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

Используя (guix monads) и (guix gexp), ее можно переписать как монадическую функцию:

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

Во второй версии следует отметить несколько моментов: параметр store теперь является неявным и является “threaded” в вызовах package->derivation и gexp->derivation монадические процедуры, а монадическим значением, возвращаемым package->derivation, является bound с использованием mlet вместо простого let.

Оказывается, вызов package->derivation можно даже опустить, поскольку она будет выполняться неявно, как мы увидим позже (see G-Expressions):

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

Вызов монадического sh-symlink ни на что не влияет. Как кто-то однажды сказал: “Вы выходите из монады, как вы выходите из горящего здания: by running”. Итак, чтобы выйти из монады и получить желаемый эффект, нужно использовать run-with-store:

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

Обратите внимание, что модуль (guix monad-repl) расширяет Guile REPL новыми “метакомандами”, чтобы упростить работу с монадическими процедурами: run-in-store и Enter-store-monad. Первая используется для “run” одного монадического значения через store:

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

Последний входит в рекурсивный REPL, где все возвращаемые значения автоматически проходят через хранилище:

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

Обратите внимание, что немонадические значения не могут быть возвращены в REPL store-monad.

Основные синтаксические формы для работы с монадами в целом предоставляются модулем (guix monads) и описаны ниже.

Scheme Syntax: with-monad monad body ...

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

Scheme Syntax: return val

Возвращает монадическое значение, инкапсулирующее val.

Scheme Syntax: >>= mval mproc ...

Bind monadic value mval, passing its “contents” to monadic procedures mproc20. 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

Вернуть текущее состояние в виде монадического значения.

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

(20)

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: , Previous: , Up: Программный интерфейс   [Contents][Index]