monads.scm 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2015, 2016, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  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.
  10. ;;;
  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.
  15. ;;;
  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/>.
  18. (define-module (test-monads)
  19. #:use-module (guix tests)
  20. #:use-module (guix store)
  21. #:use-module (guix monads)
  22. #:use-module (guix derivations)
  23. #:use-module (guix packages)
  24. #:use-module (gnu packages)
  25. #:use-module (gnu packages bootstrap)
  26. #:use-module ((gnu packages base) #:select (coreutils))
  27. #:use-module (ice-9 match)
  28. #:use-module (rnrs io ports)
  29. #:use-module (srfi srfi-1)
  30. #:use-module (srfi srfi-26)
  31. #:use-module (srfi srfi-64))
  32. ;; Test the (guix monads) module.
  33. (define %store
  34. (open-connection-for-tests))
  35. ;; Globally disable grafts because they can trigger early builds.
  36. (%graft? #f)
  37. (define %monads
  38. (list %identity-monad %store-monad %state-monad))
  39. (define %monad-run
  40. (list identity
  41. (cut run-with-store %store <>)
  42. (cut run-with-state <> '())))
  43. (define-syntax-rule (values->list exp)
  44. (call-with-values (lambda () exp)
  45. list))
  46. (test-begin "monads")
  47. (test-assert "monad?"
  48. (and (every monad? %monads)
  49. (every (compose procedure? monad-bind) %monads)
  50. (every (compose procedure? monad-return) %monads)))
  51. ;; The 3 "monad laws": <http://www.haskell.org/haskellwiki/Monad_laws>.
  52. (test-assert "left identity"
  53. (every (lambda (monad run)
  54. (let ((number (random 777)))
  55. (with-monad monad
  56. (define (f x)
  57. (return (* (1+ number) 2)))
  58. (= (run (>>= (return number) f))
  59. (run (f number))))))
  60. %monads
  61. %monad-run))
  62. (test-assert "right identity"
  63. (every (lambda (monad run)
  64. (with-monad monad
  65. (let ((number (return (random 777))))
  66. (= (run (>>= number return))
  67. (run number)))))
  68. %monads
  69. %monad-run))
  70. (test-assert "associativity"
  71. (every (lambda (monad run)
  72. (with-monad monad
  73. (define (f x)
  74. (return (+ 1 x)))
  75. (define (g x)
  76. (return (* 2 x)))
  77. (let ((number (return (random 777))))
  78. (= (run (>>= (>>= number f) g))
  79. (run (>>= number (lambda (x) (>>= (f x) g))))))))
  80. %monads
  81. %monad-run))
  82. (test-assert "lift"
  83. (every (lambda (monad run)
  84. (let ((f (lift1 1+ monad))
  85. (g (apply lift1 1+ (list monad))))
  86. (with-monad monad
  87. (let ((number (random 777)))
  88. (= (run (>>= (return number) f))
  89. (run (>>= (return number) g))
  90. (1+ number))))))
  91. %monads
  92. %monad-run))
  93. (test-assert ">>= with more than two arguments"
  94. (every (lambda (monad run)
  95. (let ((1+ (lift1 1+ monad))
  96. (2* (lift1 (cut * 2 <>) monad)))
  97. (with-monad monad
  98. (let ((number (random 777)))
  99. (= (run (>>= (return number)
  100. 1+ 1+ 1+
  101. 2* 2* 2*))
  102. (* 8 (+ number 3)))))))
  103. %monads
  104. %monad-run))
  105. (test-assert "mbegin"
  106. (every (lambda (monad run)
  107. (with-monad monad
  108. (let* ((been-there? #f)
  109. (number (mbegin monad
  110. (return 1)
  111. (begin
  112. (set! been-there? #t)
  113. (return 2))
  114. (return 3))))
  115. (and (= (run number) 3)
  116. been-there?))))
  117. %monads
  118. %monad-run))
  119. (test-assert "mparameterize"
  120. (let ((parameter (make-parameter 'outside)))
  121. (every (lambda (monad run)
  122. (equal?
  123. (run (mlet monad ((outer (return (parameter)))
  124. (inner
  125. (mparameterize monad ((parameter 'inside))
  126. (return (parameter)))))
  127. (return (list outer inner (parameter)))))
  128. '(outside inside outside)))
  129. %monads
  130. %monad-run)))
  131. (test-assert "mlet* + text-file + package-file"
  132. (run-with-store %store
  133. (mlet* %store-monad ((guile (package-file %bootstrap-guile "bin/guile"))
  134. (file (text-file "monadic" guile)))
  135. (return (equal? (call-with-input-file file get-string-all)
  136. guile)))
  137. #:guile-for-build (package-derivation %store %bootstrap-guile)))
  138. (test-assert "package-file, default system"
  139. ;; The default system should be the one at '>>=' time, not the one at
  140. ;; invocation time. See <http://bugs.gnu.org/18002>.
  141. (run-with-store %store
  142. (mlet* %store-monad
  143. ((system -> (%current-system))
  144. (file (parameterize ((%current-system "foobar64-linux"))
  145. (package-file coreutils "bin/ls")))
  146. (cu (package->derivation coreutils)))
  147. (return (string=? file
  148. (string-append (derivation->output-path cu)
  149. "/bin/ls"))))
  150. #:guile-for-build (package-derivation %store %bootstrap-guile)))
  151. (test-assert "package-file + package->cross-derivation"
  152. (run-with-store %store
  153. (mlet* %store-monad ((target -> "mips64el-linux-gnu")
  154. (file (package-file coreutils "bin/ls"
  155. #:target target))
  156. (xcu (package->cross-derivation coreutils target)))
  157. (let ((output (derivation->output-path xcu)))
  158. (return (string=? file (string-append output "/bin/ls")))))
  159. #:guile-for-build (package-derivation %store %bootstrap-guile)))
  160. (test-assert "interned-file"
  161. (run-with-store %store
  162. (mlet* %store-monad ((file -> (search-path %load-path "guix.scm"))
  163. (a (interned-file file))
  164. (b (interned-file file "b")))
  165. (return (equal? (call-with-input-file file get-string-all)
  166. (call-with-input-file a get-string-all)
  167. (call-with-input-file b get-string-all))))
  168. #:guile-for-build (package-derivation %store %bootstrap-guile)))
  169. (test-assert "mapm"
  170. (every (lambda (monad run)
  171. (with-monad monad
  172. (equal? (run (mapm monad (lift1 1+ monad) (iota 10)))
  173. (map 1+ (iota 10)))))
  174. %monads
  175. %monad-run))
  176. (test-assert "sequence"
  177. (every (lambda (monad run)
  178. (let* ((input (iota 100))
  179. (order '()))
  180. (define (frob i)
  181. (mlet monad ((foo (return 'foo)))
  182. ;; The side effect here is used to keep track of the order in
  183. ;; which monadic values are bound. Perform the side effect
  184. ;; within a '>>=' so that it is performed when the return
  185. ;; value is actually bound.
  186. (set! order (cons i order))
  187. (return i)))
  188. (and (equal? input
  189. (run (sequence monad (map frob input))))
  190. ;; Make sure this is from left to right.
  191. (equal? order (reverse input)))))
  192. %monads
  193. %monad-run))
  194. (test-assert "listm"
  195. (every (lambda (monad run)
  196. (run (with-monad monad
  197. (let ((lst (listm monad
  198. (return 1) (return 2) (return 3))))
  199. (mlet monad ((lst lst))
  200. (return (equal? '(1 2 3) lst)))))))
  201. %monads
  202. %monad-run))
  203. (test-assert "anym"
  204. (every (lambda (monad run)
  205. (eq? (run (with-monad monad
  206. (anym monad
  207. (lift1 (lambda (x)
  208. (and (odd? x) 'odd!))
  209. monad)
  210. (append (make-list 1000 0)
  211. (list 1 2)))))
  212. 'odd!))
  213. %monads
  214. %monad-run))
  215. (test-equal "set-current-state"
  216. (list '(a a d) 'd)
  217. (values->list
  218. (run-with-state
  219. (mlet* %state-monad ((init (current-state))
  220. (init2 (set-current-state 'b)))
  221. (mbegin %state-monad
  222. (set-current-state 'c)
  223. (set-current-state 'd)
  224. (mlet %state-monad ((last (current-state)))
  225. (return (list init init2 last)))))
  226. 'a)))
  227. (test-equal "state-push etc."
  228. (list '((z . 2) (p . (1)) (a . (1))) '(2 1))
  229. (values->list
  230. (run-with-state
  231. (mbegin %state-monad
  232. (state-push 1) ;(1)
  233. (state-push 2) ;(2 1)
  234. (mlet* %state-monad ((z (state-pop)) ;(1)
  235. (p (current-state))
  236. (a (state-push z))) ;(2 1)
  237. (return `((z . ,z) (p . ,p) (a . ,a)))))
  238. '())))
  239. (test-end "monads")