monads.scm 8.9 KB

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