1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2014, 2015, 2016, 2022 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19 (define-module (test-monads)
20 #:use-module (guix tests)
21 #:use-module (guix store)
22 #:use-module (guix monads)
23 #:use-module (guix grafts)
24 #:use-module (guix derivations)
25 #:use-module (guix packages)
26 #:use-module (gnu packages)
27 #:use-module (gnu packages bootstrap)
28 #:use-module ((gnu packages base) #:select (coreutils))
29 #:use-module (ice-9 match)
30 #:use-module (rnrs io ports)
31 #:use-module (srfi srfi-1)
32 #:use-module (srfi srfi-26)
33 #:use-module (srfi srfi-64))
35 ;; Test the (guix monads) module.
38 (open-connection-for-tests))
40 ;; Globally disable grafts because they can trigger early builds.
44 (list %identity-monad %store-monad %state-monad))
48 (cut run-with-store %store <>)
49 (cut run-with-state <> '())))
51 (define-syntax-rule (values->list exp)
52 (call-with-values (lambda () exp)
59 (and (every monad? %monads)
60 (every (compose procedure? monad-bind) %monads)
61 (every (compose procedure? monad-return) %monads)))
63 ;; The 3 "monad laws": <http://www.haskell.org/haskellwiki/Monad_laws>.
65 (test-assert "left identity"
66 (every (lambda (monad run)
67 (let ((number (random 777)))
70 (return (* (1+ number) 2)))
72 (= (run (>>= (return number) f))
77 (test-assert "right identity"
78 (every (lambda (monad run)
80 (let ((number (return (random 777))))
81 (= (run (>>= number return))
86 (test-assert "associativity"
87 (every (lambda (monad run)
94 (let ((number (return (random 777))))
95 (= (run (>>= (>>= number f) g))
96 (run (>>= number (lambda (x) (>>= (f x) g))))))))
101 (every (lambda (monad run)
102 (let ((f (lift1 1+ monad))
103 (g (apply lift1 1+ (list monad))))
105 (let ((number (random 777)))
106 (= (run (>>= (return number) f))
107 (run (>>= (return number) g))
112 (test-assert ">>= with more than two arguments"
113 (every (lambda (monad run)
114 (let ((1+ (lift1 1+ monad))
115 (2* (lift1 (cut * 2 <>) monad)))
117 (let ((number (random 777)))
118 (= (run (>>= (return number)
121 (* 8 (+ number 3)))))))
125 (test-assert "mbegin"
126 (every (lambda (monad run)
128 (let* ((been-there? #f)
129 (number (mbegin monad
132 (set! been-there? #t)
135 (and (= (run number) 3)
140 (test-assert "mparameterize"
141 (let ((parameter (make-parameter 'outside)))
142 (every (lambda (monad run)
144 (run (mlet monad ((outer (return (parameter)))
146 (mparameterize monad ((parameter 'inside))
147 (return (parameter)))))
148 (return (list outer inner (parameter)))))
149 '(outside inside outside)))
153 (test-assert "mlet* + text-file + package-file"
154 (run-with-store %store
155 (mlet* %store-monad ((guile (package-file %bootstrap-guile "bin/guile"))
156 (file (text-file "monadic" guile)))
157 (return (equal? (call-with-input-file file get-string-all)
159 #:guile-for-build (package-derivation %store %bootstrap-guile)))
161 (test-assert "package-file, default system"
162 ;; The default system should be the one at '>>=' time, not the one at
163 ;; invocation time. See <http://bugs.gnu.org/18002>.
164 (run-with-store %store
166 ((system -> (%current-system))
167 (file (parameterize ((%current-system "foobar64-linux"))
168 (package-file coreutils "bin/ls")))
169 (cu (package->derivation coreutils)))
170 (return (string=? file
171 (string-append (derivation->output-path cu)
173 #:guile-for-build (package-derivation %store %bootstrap-guile)))
175 (test-assert "package-file + package->cross-derivation"
176 (run-with-store %store
177 (mlet* %store-monad ((target -> "mips64el-linux-gnu")
178 (file (package-file coreutils "bin/ls"
180 (xcu (package->cross-derivation coreutils target)))
181 (let ((output (derivation->output-path xcu)))
182 (return (string=? file (string-append output "/bin/ls")))))
183 #:guile-for-build (package-derivation %store %bootstrap-guile)))
185 (test-assert "interned-file"
186 (run-with-store %store
187 (mlet* %store-monad ((file -> (search-path %load-path "guix.scm"))
188 (a (interned-file file))
189 (b (interned-file file "b")))
190 (return (equal? (call-with-input-file file get-string-all)
191 (call-with-input-file a get-string-all)
192 (call-with-input-file b get-string-all))))
193 #:guile-for-build (package-derivation %store %bootstrap-guile)))
196 (every (lambda (monad run)
198 (equal? (run (mapm monad (lift1 1+ monad) (iota 10)))
199 (map 1+ (iota 10)))))
203 (test-assert "sequence"
204 (every (lambda (monad run)
205 (let* ((input (iota 100))
208 (mlet monad ((foo (return 'foo)))
209 ;; The side effect here is used to keep track of the order in
210 ;; which monadic values are bound. Perform the side effect
211 ;; within a '>>=' so that it is performed when the return
212 ;; value is actually bound.
213 (set! order (cons i order))
217 (run (sequence monad (map frob input))))
219 ;; Make sure this is from left to right.
220 (equal? order (reverse input)))))
225 (every (lambda (monad run)
226 (run (with-monad monad
227 (let ((lst (listm monad
228 (return 1) (return 2) (return 3))))
229 (mlet monad ((lst lst))
230 (return (equal? '(1 2 3) lst)))))))
235 (every (lambda (monad run)
236 (eq? (run (with-monad monad
239 (and (odd? x) 'odd!))
241 (append (make-list 1000 0)
247 (test-equal "set-current-state"
251 (mlet* %state-monad ((init (current-state))
252 (init2 (set-current-state 'b)))
254 (set-current-state 'c)
255 (set-current-state 'd)
256 (mlet %state-monad ((last (current-state)))
257 (return (list init init2 last)))))
260 (test-equal "state-push etc."
261 (list '((z . 2) (p . (1)) (a . (1))) '(2 1))
266 (state-push 2) ;(2 1)
267 (mlet* %state-monad ((z (state-pop)) ;(1)
269 (a (state-push z))) ;(2 1)
270 (return `((z . ,z) (p . ,p) (a . ,a)))))