monads: Allow n-ary '>>=' expressions.

Suggested by Federico Beffa <beffa@fbengineering.ch>.

* guix/monads.scm (bind-syntax): New macro.
  (with-monad): Use it instead of 'identifier-syntax'.
* tests/monads.scm (">>= with more than two arguments"): New test.
* doc/guix.texi (The Store Monad): Explain that there can be several MPROC.
  Add an example.
This commit is contained in:
Ludovic Courtès 2015-06-08 22:49:50 +02:00
parent ae9b96c784
commit 751630c9c3
3 changed files with 56 additions and 7 deletions

View file

@ -2773,12 +2773,25 @@ in @var{monad}.
Return a monadic value that encapsulates @var{val}. Return a monadic value that encapsulates @var{val}.
@end deffn @end deffn
@deffn {Scheme Syntax} >>= @var{mval} @var{mproc} @deffn {Scheme Syntax} >>= @var{mval} @var{mproc} ...
@dfn{Bind} monadic value @var{mval}, passing its ``contents'' to monadic @dfn{Bind} monadic value @var{mval}, passing its ``contents'' to monadic
procedure @var{mproc}@footnote{This operation is commonly referred to as procedures @var{mproc}@dots{}@footnote{This operation is commonly
``bind'', but that name denotes an unrelated procedure in Guile. Thus referred to as ``bind'', but that name denotes an unrelated procedure in
we use this somewhat cryptic symbol inherited from the Haskell Guile. Thus we use this somewhat cryptic symbol inherited from the
language.}. Haskell language.}. There can be one @var{mproc} or several of them, as
in this example:
@example
(run-with-state
(with-monad %state-monad
(>>= (return 1)
(lambda (x) (return (+ 1 x)))
(lambda (x) (return (* 2 x)))))
'some-state)
@result{} 4
@result{} some-state
@end example
@end deffn @end deffn
@deffn {Scheme Syntax} mlet @var{monad} ((@var{var} @var{mval}) ...) @ @deffn {Scheme Syntax} mlet @var{monad} ((@var{var} @var{mval}) ...) @

View file

@ -112,6 +112,29 @@ (define-syntax-parameter return
(lambda (s) (lambda (s)
(syntax-violation 'return "return used outside of 'with-monad'" s))) (syntax-violation 'return "return used outside of 'with-monad'" s)))
(define-syntax-rule (bind-syntax bind)
"Return a macro transformer that handles the expansion of '>>=' expressions
using BIND as the binary bind operator.
This macro exists to allow the expansion of n-ary '>>=' expressions, even
though BIND is simply binary, as in:
(with-monad %state-monad
(>>= (return 1)
(lift 1+ %state-monad)
(lift 1+ %state-monad)))
"
(lambda (stx)
(define (expand body)
(syntax-case body ()
((_ mval mproc)
#'(bind mval mproc))
((x mval mproc0 mprocs (... ...))
(expand #'(>>= (>>= mval mproc0)
mprocs (... ...))))))
(expand stx)))
(define-syntax with-monad (define-syntax with-monad
(lambda (s) (lambda (s)
"Evaluate BODY in the context of MONAD, and return its result." "Evaluate BODY in the context of MONAD, and return its result."
@ -120,13 +143,13 @@ (define-syntax with-monad
(eq? 'macro (syntax-local-binding #'monad)) (eq? 'macro (syntax-local-binding #'monad))
;; MONAD is a syntax transformer, so we can obtain the bind and return ;; MONAD is a syntax transformer, so we can obtain the bind and return
;; methods by directly querying it. ;; methods by directly querying it.
#'(syntax-parameterize ((>>= (identifier-syntax (monad %bind))) #'(syntax-parameterize ((>>= (bind-syntax (monad %bind)))
(return (identifier-syntax (monad %return)))) (return (identifier-syntax (monad %return))))
body ...)) body ...))
((_ monad body ...) ((_ monad body ...)
;; MONAD refers to the <monad> record that represents the monad at run ;; MONAD refers to the <monad> record that represents the monad at run
;; time, so use the slow method. ;; time, so use the slow method.
#'(syntax-parameterize ((>>= (identifier-syntax #'(syntax-parameterize ((>>= (bind-syntax
(monad-bind monad))) (monad-bind monad)))
(return (identifier-syntax (return (identifier-syntax
(monad-return monad)))) (monad-return monad))))

View file

@ -103,6 +103,19 @@ (define (g x)
%monads %monads
%monad-run)) %monad-run))
(test-assert ">>= with more than two arguments"
(every (lambda (monad run)
(let ((1+ (lift1 1+ monad))
(2* (lift1 (cut * 2 <>) monad)))
(with-monad monad
(let ((number (random 777)))
(= (run (>>= (return number)
1+ 1+ 1+
2* 2* 2*))
(* 8 (+ number 3)))))))
%monads
%monad-run))
(test-assert "mbegin" (test-assert "mbegin"
(every (lambda (monad run) (every (lambda (monad run)
(with-monad monad (with-monad monad