2013-10-03 22:45:25 +02:00
|
|
|
|
;;; GNU Guix --- Functional package management for GNU
|
2022-07-10 12:39:44 +02:00
|
|
|
|
;;; Copyright © 2013, 2014, 2015, 2017, 2022 Ludovic Courtès <ludo@gnu.org>
|
2013-10-03 22:45:25 +02:00
|
|
|
|
;;;
|
|
|
|
|
;;; This file is part of GNU Guix.
|
|
|
|
|
;;;
|
|
|
|
|
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
|
|
|
|
;;; under the terms of the GNU General Public License as published by
|
|
|
|
|
;;; the Free Software Foundation; either version 3 of the License, or (at
|
|
|
|
|
;;; your option) any later version.
|
|
|
|
|
;;;
|
|
|
|
|
;;; GNU Guix is distributed in the hope that it will be useful, but
|
|
|
|
|
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;;; GNU General Public License for more details.
|
|
|
|
|
;;;
|
|
|
|
|
;;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
(define-module (guix monads)
|
2013-10-02 21:58:19 +02:00
|
|
|
|
#:use-module ((system syntax)
|
|
|
|
|
#:select (syntax-local-binding))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
#:use-module (ice-9 match)
|
2014-02-03 23:12:54 +01:00
|
|
|
|
#:use-module (srfi srfi-1)
|
2013-10-02 21:58:19 +02:00
|
|
|
|
#:use-module (srfi srfi-9)
|
2013-10-03 22:45:25 +02:00
|
|
|
|
#:use-module (srfi srfi-26)
|
|
|
|
|
#:export (;; Monads.
|
2013-10-02 21:58:19 +02:00
|
|
|
|
define-monad
|
2013-10-03 22:45:25 +02:00
|
|
|
|
monad?
|
|
|
|
|
monad-bind
|
|
|
|
|
monad-return
|
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
template-directory
|
|
|
|
|
|
2013-10-03 22:45:25 +02:00
|
|
|
|
;; Syntax.
|
|
|
|
|
>>=
|
|
|
|
|
return
|
|
|
|
|
with-monad
|
|
|
|
|
mlet
|
|
|
|
|
mlet*
|
2014-10-08 23:35:08 +02:00
|
|
|
|
mbegin
|
2014-12-02 10:11:11 +01:00
|
|
|
|
mwhen
|
|
|
|
|
munless
|
2022-07-10 12:39:44 +02:00
|
|
|
|
mparameterize
|
2014-12-02 10:10:51 +01:00
|
|
|
|
lift0 lift1 lift2 lift3 lift4 lift5 lift6 lift7 lift
|
2013-10-03 22:45:25 +02:00
|
|
|
|
listm
|
|
|
|
|
foldm
|
|
|
|
|
mapm
|
|
|
|
|
sequence
|
|
|
|
|
anym
|
|
|
|
|
|
|
|
|
|
;; Concrete monads.
|
2015-01-17 18:46:41 +01:00
|
|
|
|
%identity-monad
|
|
|
|
|
|
|
|
|
|
%state-monad
|
|
|
|
|
state-return
|
|
|
|
|
state-bind
|
|
|
|
|
current-state
|
|
|
|
|
set-current-state
|
|
|
|
|
state-push
|
|
|
|
|
state-pop
|
|
|
|
|
run-with-state))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
;;;
|
|
|
|
|
;;; This module implements the general mechanism of monads, and provides in
|
2015-01-17 23:43:41 +01:00
|
|
|
|
;;; particular an instance of the "state" monad. The API was inspired by that
|
2013-10-03 22:45:25 +02:00
|
|
|
|
;;; of Racket's "better-monads" module (see
|
|
|
|
|
;;; <http://planet.racket-lang.org/package-source/toups/functional.plt/1/1/planet-docs/better-monads-guide/index.html>).
|
|
|
|
|
;;; The implementation and use case were influenced by Oleg Kysielov's
|
|
|
|
|
;;; "Monadic Programming in Scheme" (see
|
|
|
|
|
;;; <http://okmij.org/ftp/Scheme/monad-in-Scheme.html>).
|
|
|
|
|
;;;
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
;; Record type for monads manipulated at run time.
|
|
|
|
|
(define-record-type <monad>
|
|
|
|
|
(make-monad bind return)
|
2013-10-03 22:45:25 +02:00
|
|
|
|
monad?
|
|
|
|
|
(bind monad-bind)
|
|
|
|
|
(return monad-return)) ; TODO: Add 'plus' and 'zero'
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(define-syntax define-monad
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"Define the monad under NAME, with the given bind and return methods."
|
|
|
|
|
(define prefix (string->symbol "% "))
|
|
|
|
|
(define (make-rtd-name name)
|
|
|
|
|
(datum->syntax name
|
|
|
|
|
(symbol-append prefix (syntax->datum name) '-rtd)))
|
|
|
|
|
|
|
|
|
|
(syntax-case s (bind return)
|
|
|
|
|
((_ name (bind b) (return r))
|
|
|
|
|
(with-syntax ((rtd (make-rtd-name #'name)))
|
|
|
|
|
#`(begin
|
|
|
|
|
(define rtd
|
|
|
|
|
;; The record type, for use at run time.
|
|
|
|
|
(make-monad b r))
|
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
;; Instantiate all the templates, specialized for this monad.
|
|
|
|
|
(template-directory instantiations name)
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(define-syntax name
|
|
|
|
|
;; An "inlined record", for use at expansion time. The goal is
|
|
|
|
|
;; to allow 'bind' and 'return' to be resolved at expansion
|
|
|
|
|
;; time, in the common case where the monad is accessed
|
|
|
|
|
;; directly as NAME.
|
|
|
|
|
(lambda (s)
|
|
|
|
|
(syntax-case s (%bind %return)
|
|
|
|
|
((_ %bind) #'b)
|
|
|
|
|
((_ %return) #'r)
|
|
|
|
|
(_ #'rtd))))))))))
|
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
;; Expansion- and run-time state of the template directory. This needs to be
|
|
|
|
|
;; available at run time (and not just at expansion time) so we can
|
|
|
|
|
;; instantiate templates defined in other modules, or use instances defined
|
|
|
|
|
;; elsewhere.
|
|
|
|
|
(eval-when (load expand eval)
|
|
|
|
|
;; Mapping of syntax objects denoting the template to a pair containing (1)
|
|
|
|
|
;; the syntax object of the parameter over which it is templated, and (2)
|
|
|
|
|
;; the syntax of its body.
|
|
|
|
|
(define-once %templates (make-hash-table))
|
|
|
|
|
|
|
|
|
|
(define (register-template! name param body)
|
|
|
|
|
(hash-set! %templates name (cons param body)))
|
|
|
|
|
|
|
|
|
|
;; List of template instances, where each entry is a triplet containing the
|
|
|
|
|
;; syntax of the name, the actual parameter for which the template is
|
|
|
|
|
;; specialized, and the syntax object referring to this specialization (the
|
|
|
|
|
;; procedure's identifier.)
|
|
|
|
|
(define-once %template-instances '())
|
|
|
|
|
|
|
|
|
|
(define (register-template-instance! name actual instance)
|
|
|
|
|
(set! %template-instances
|
|
|
|
|
(cons (list name actual instance) %template-instances))))
|
|
|
|
|
|
|
|
|
|
(define-syntax template-directory
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"This is a \"stateful macro\" to register and lookup templates and
|
|
|
|
|
template instances."
|
|
|
|
|
(define location
|
|
|
|
|
(syntax-source s))
|
|
|
|
|
|
|
|
|
|
(define current-info-port
|
|
|
|
|
;; Port for debugging info.
|
|
|
|
|
(const (%make-void-port "w")))
|
|
|
|
|
|
|
|
|
|
(define location-string
|
|
|
|
|
(format #f "~a:~a:~a"
|
|
|
|
|
(assq-ref location 'filename)
|
|
|
|
|
(and=> (assq-ref location 'line) 1+)
|
|
|
|
|
(assq-ref location 'column)))
|
|
|
|
|
|
|
|
|
|
(define (matching-instance? name actual)
|
|
|
|
|
(match-lambda
|
|
|
|
|
((name* instance-param proc)
|
|
|
|
|
(and (free-identifier=? name name*)
|
|
|
|
|
(or (equal? actual instance-param)
|
|
|
|
|
(and (identifier? actual)
|
|
|
|
|
(identifier? instance-param)
|
|
|
|
|
(free-identifier=? instance-param
|
|
|
|
|
actual)))
|
|
|
|
|
proc))))
|
|
|
|
|
|
|
|
|
|
(define (instance-identifier name actual)
|
|
|
|
|
(define stem
|
|
|
|
|
(string-append
|
|
|
|
|
" "
|
|
|
|
|
(symbol->string (syntax->datum name))
|
|
|
|
|
(if (identifier? actual)
|
|
|
|
|
(string-append " " (symbol->string (syntax->datum actual)))
|
|
|
|
|
"")
|
|
|
|
|
" instance"))
|
|
|
|
|
(datum->syntax actual (string->symbol stem)))
|
|
|
|
|
|
|
|
|
|
(define (instance-definition name template actual)
|
|
|
|
|
(match template
|
|
|
|
|
((formal . body)
|
|
|
|
|
(let ((instance (instance-identifier name actual)))
|
|
|
|
|
(format (current-info-port)
|
|
|
|
|
"~a: info: specializing '~a' for '~a' as '~a'~%"
|
|
|
|
|
location-string
|
|
|
|
|
(syntax->datum name) (syntax->datum actual)
|
|
|
|
|
(syntax->datum instance))
|
|
|
|
|
|
|
|
|
|
(register-template-instance! name actual instance)
|
|
|
|
|
|
|
|
|
|
#`(begin
|
|
|
|
|
(define #,instance
|
|
|
|
|
(let-syntax ((#,formal (identifier-syntax #,actual)))
|
|
|
|
|
#,body))
|
|
|
|
|
|
|
|
|
|
;; Generate code to register the thing at run time.
|
|
|
|
|
(register-template-instance! #'#,name #'#,actual
|
|
|
|
|
#'#,instance))))))
|
|
|
|
|
|
|
|
|
|
(syntax-case s (register! lookup exists? instantiations)
|
|
|
|
|
((_ register! name param body)
|
|
|
|
|
;; Register NAME as a template on PARAM with the given BODY.
|
|
|
|
|
(begin
|
|
|
|
|
(register-template! #'name #'param #'body)
|
|
|
|
|
|
|
|
|
|
;; Generate code to register the template at run time. XXX: Because
|
|
|
|
|
;; of this, BODY must not contain ellipses.
|
|
|
|
|
#'(register-template! #'name #'param #'body)))
|
|
|
|
|
((_ lookup name actual)
|
|
|
|
|
;; Search for an instance of template NAME for this ACTUAL parameter.
|
|
|
|
|
;; On success, expand to the identifier of the instance; otherwise
|
|
|
|
|
;; expand to #f.
|
|
|
|
|
(any (matching-instance? #'name #'actual) %template-instances))
|
|
|
|
|
((_ exists? name actual)
|
|
|
|
|
;; Likewise, but return a Boolean.
|
|
|
|
|
(let ((result (->bool
|
|
|
|
|
(any (matching-instance? #'name #'actual)
|
|
|
|
|
%template-instances))))
|
|
|
|
|
(unless result
|
|
|
|
|
(format (current-warning-port)
|
|
|
|
|
"~a: warning: no specialization of template '~a' for '~a'~%"
|
|
|
|
|
location-string
|
|
|
|
|
(syntax->datum #'name) (syntax->datum #'actual)))
|
|
|
|
|
result))
|
|
|
|
|
((_ instantiations actual)
|
|
|
|
|
;; Expand to the definitions of all the existing templates
|
|
|
|
|
;; specialized for ACTUAL.
|
|
|
|
|
#`(begin
|
|
|
|
|
#,@(hash-map->list (cut instance-definition <> <> #'actual)
|
|
|
|
|
%templates))))))
|
|
|
|
|
|
|
|
|
|
(define-syntax define-template
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"Define a template, which is a procedure that can be specialized over its
|
|
|
|
|
first argument. In our case, the first argument is typically the identifier
|
|
|
|
|
of a monad.
|
|
|
|
|
|
|
|
|
|
Defining templates for procedures like 'mapm' allows us to make have a
|
|
|
|
|
specialized version of those procedures for each monad that we define, such
|
|
|
|
|
that calls to:
|
|
|
|
|
|
|
|
|
|
(mapm %state-monad proc lst)
|
|
|
|
|
|
|
|
|
|
automatically expand to:
|
|
|
|
|
|
|
|
|
|
(#{ mapm %state-monad instance}# proc lst)
|
|
|
|
|
|
|
|
|
|
Here, #{ mapm %state-monad instance}# is specialized for %state-monad, and
|
|
|
|
|
thus it contains inline calls to %state-bind and %state-return. This avoids
|
|
|
|
|
repeated calls to 'struct-ref' to get the 'bind' and 'return' procedure of the
|
|
|
|
|
monad, and allows 'bind' and 'return' to be inlined, which in turn allows for
|
|
|
|
|
more optimizations."
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((_ (name arg0 args ...) body ...)
|
|
|
|
|
(with-syntax ((generic-name (datum->syntax
|
|
|
|
|
#'name
|
|
|
|
|
(symbol-append '#{ %}#
|
|
|
|
|
(syntax->datum #'name)
|
|
|
|
|
'-generic)))
|
|
|
|
|
(original-name #'name))
|
|
|
|
|
#`(begin
|
|
|
|
|
(template-directory register! name arg0
|
|
|
|
|
(lambda (args ...)
|
|
|
|
|
body ...))
|
|
|
|
|
(define (generic-name arg0 args ...)
|
|
|
|
|
;; The generic instance of NAME, for when no specialization was
|
|
|
|
|
;; found.
|
|
|
|
|
body ...)
|
|
|
|
|
|
|
|
|
|
(define-syntax name
|
|
|
|
|
(lambda (s)
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((_ arg0* args ...)
|
|
|
|
|
;; Expand to either the specialized instance or the
|
|
|
|
|
;; generic instance of template ORIGINAL-NAME.
|
|
|
|
|
#'(if (template-directory exists? original-name arg0*)
|
|
|
|
|
((template-directory lookup original-name arg0*)
|
|
|
|
|
args ...)
|
|
|
|
|
(generic-name arg0* args ...)))
|
|
|
|
|
(_
|
|
|
|
|
#'generic-name))))))))))
|
|
|
|
|
|
2020-12-17 16:19:07 +01:00
|
|
|
|
(define-syntax-parameter >>=
|
2013-10-03 22:45:25 +02:00
|
|
|
|
;; The name 'bind' is already taken, so we choose this (obscure) symbol.
|
|
|
|
|
(lambda (s)
|
|
|
|
|
(syntax-violation '>>= ">>= (bind) used outside of 'with-monad'" s)))
|
|
|
|
|
|
2020-12-17 16:19:07 +01:00
|
|
|
|
(define-syntax-parameter return
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(lambda (s)
|
|
|
|
|
(syntax-violation 'return "return used outside of 'with-monad'" s)))
|
|
|
|
|
|
2015-06-08 22:49:50 +02:00
|
|
|
|
(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)))
|
|
|
|
|
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(define-syntax with-monad
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"Evaluate BODY in the context of MONAD, and return its result."
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((_ monad body ...)
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(eq? 'macro (syntax-local-binding #'monad))
|
|
|
|
|
;; MONAD is a syntax transformer, so we can obtain the bind and return
|
|
|
|
|
;; methods by directly querying it.
|
2015-06-08 22:49:50 +02:00
|
|
|
|
#'(syntax-parameterize ((>>= (bind-syntax (monad %bind)))
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(return (identifier-syntax (monad %return))))
|
|
|
|
|
body ...))
|
|
|
|
|
((_ monad body ...)
|
|
|
|
|
;; MONAD refers to the <monad> record that represents the monad at run
|
|
|
|
|
;; time, so use the slow method.
|
2015-06-08 22:49:50 +02:00
|
|
|
|
#'(syntax-parameterize ((>>= (bind-syntax
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(monad-bind monad)))
|
|
|
|
|
(return (identifier-syntax
|
|
|
|
|
(monad-return monad))))
|
|
|
|
|
body ...)))))
|
|
|
|
|
|
|
|
|
|
(define-syntax mlet*
|
|
|
|
|
(syntax-rules (->)
|
|
|
|
|
"Bind the given monadic values MVAL to the given variables VAR. When the
|
|
|
|
|
form is (VAR -> VAL), bind VAR to the non-monadic value VAL in the same way as
|
|
|
|
|
'let'."
|
|
|
|
|
;; Note: the '->' symbol corresponds to 'is:' in 'better-monads.rkt'.
|
|
|
|
|
((_ monad () body ...)
|
|
|
|
|
(with-monad monad body ...))
|
|
|
|
|
((_ monad ((var mval) rest ...) body ...)
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(>>= mval
|
|
|
|
|
(lambda (var)
|
|
|
|
|
(mlet* monad (rest ...)
|
|
|
|
|
body ...)))))
|
|
|
|
|
((_ monad ((var -> val) rest ...) body ...)
|
|
|
|
|
(let ((var val))
|
|
|
|
|
(mlet* monad (rest ...)
|
|
|
|
|
body ...)))))
|
|
|
|
|
|
|
|
|
|
(define-syntax mlet
|
|
|
|
|
(lambda (s)
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((_ monad ((var mval ...) ...) body ...)
|
|
|
|
|
(with-syntax (((temp ...) (generate-temporaries #'(var ...))))
|
|
|
|
|
#'(mlet* monad ((temp mval ...) ...)
|
|
|
|
|
(let ((var temp) ...)
|
|
|
|
|
body ...)))))))
|
|
|
|
|
|
2014-10-08 23:35:08 +02:00
|
|
|
|
(define-syntax mbegin
|
2014-12-02 10:11:11 +01:00
|
|
|
|
(syntax-rules (%current-monad)
|
2017-04-06 11:28:36 +02:00
|
|
|
|
"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."
|
2014-12-02 10:11:11 +01:00
|
|
|
|
((_ %current-monad mexp)
|
|
|
|
|
mexp)
|
|
|
|
|
((_ %current-monad mexp rest ...)
|
|
|
|
|
(>>= mexp
|
|
|
|
|
(lambda (unused-value)
|
|
|
|
|
(mbegin %current-monad rest ...))))
|
2014-10-08 23:35:08 +02:00
|
|
|
|
((_ monad mexp)
|
|
|
|
|
(with-monad monad
|
|
|
|
|
mexp))
|
|
|
|
|
((_ monad mexp rest ...)
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(>>= mexp
|
|
|
|
|
(lambda (unused-value)
|
|
|
|
|
(mbegin monad rest ...)))))))
|
|
|
|
|
|
2014-12-02 10:11:11 +01:00
|
|
|
|
(define-syntax mwhen
|
|
|
|
|
(syntax-rules ()
|
2017-04-06 11:28:35 +02:00
|
|
|
|
"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."
|
2017-04-06 11:28:34 +02:00
|
|
|
|
((_ condition mexp0 mexp* ...)
|
2014-12-02 10:11:11 +01:00
|
|
|
|
(if condition
|
|
|
|
|
(mbegin %current-monad
|
2017-04-06 11:28:34 +02:00
|
|
|
|
mexp0 mexp* ...)
|
2014-12-02 10:11:11 +01:00
|
|
|
|
(return *unspecified*)))))
|
|
|
|
|
|
|
|
|
|
(define-syntax munless
|
|
|
|
|
(syntax-rules ()
|
2017-04-06 11:28:35 +02:00
|
|
|
|
"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."
|
2017-04-06 11:28:34 +02:00
|
|
|
|
((_ condition mexp0 mexp* ...)
|
2014-12-02 10:11:11 +01:00
|
|
|
|
(if condition
|
|
|
|
|
(return *unspecified*)
|
|
|
|
|
(mbegin %current-monad
|
2017-04-06 11:28:34 +02:00
|
|
|
|
mexp0 mexp* ...)))))
|
2014-12-02 10:11:11 +01:00
|
|
|
|
|
2022-07-10 12:39:44 +02:00
|
|
|
|
(define-syntax mparameterize
|
|
|
|
|
(syntax-rules ()
|
|
|
|
|
"This form implements dynamic scoping, similar to 'parameterize', but in a
|
|
|
|
|
monadic context."
|
|
|
|
|
((_ monad ((parameter value) rest ...) body ...)
|
|
|
|
|
(let ((old-value (parameter)))
|
|
|
|
|
(mbegin monad
|
|
|
|
|
;; XXX: Non-local exits are not correctly handled.
|
|
|
|
|
(return (parameter value))
|
|
|
|
|
(mlet monad ((result (mparameterize monad (rest ...) body ...)))
|
|
|
|
|
(parameter old-value)
|
|
|
|
|
(return result)))))
|
|
|
|
|
((_ monad () body ...)
|
|
|
|
|
(mbegin monad body ...))))
|
|
|
|
|
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(define-syntax define-lift
|
|
|
|
|
(syntax-rules ()
|
|
|
|
|
((_ liftn (args ...))
|
2015-08-28 15:17:20 +02:00
|
|
|
|
(define-syntax liftn
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"Lift PROC to MONAD---i.e., return a monadic function in MONAD."
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((liftn proc monad)
|
|
|
|
|
;; Inline the result of lifting PROC, such that 'return' can in
|
|
|
|
|
;; turn be open-coded.
|
|
|
|
|
#'(lambda (args ...)
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(return (proc args ...)))))
|
|
|
|
|
(id
|
|
|
|
|
(identifier? #'id)
|
|
|
|
|
;; Slow path: Return a closure-returning procedure (we don't
|
|
|
|
|
;; guarantee (eq? LIFTN LIFTN), but that's fine.)
|
2015-09-04 18:31:06 +02:00
|
|
|
|
#'(lambda (proc monad)
|
|
|
|
|
(lambda (args ...)
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(return (proc args ...))))))))))))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
2014-12-02 10:10:51 +01:00
|
|
|
|
(define-lift lift0 ())
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(define-lift lift1 (a))
|
|
|
|
|
(define-lift lift2 (a b))
|
|
|
|
|
(define-lift lift3 (a b c))
|
|
|
|
|
(define-lift lift4 (a b c d))
|
|
|
|
|
(define-lift lift5 (a b c d e))
|
|
|
|
|
(define-lift lift6 (a b c d e f))
|
|
|
|
|
(define-lift lift7 (a b c d e f g))
|
|
|
|
|
|
2014-11-05 22:25:09 +01:00
|
|
|
|
(define (lift proc monad)
|
|
|
|
|
"Lift PROC, a procedure that accepts an arbitrary number of arguments, to
|
|
|
|
|
MONAD---i.e., return a monadic function in MONAD."
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(lambda args
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(return (apply proc args)))))
|
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
(define-template (foldm monad mproc init lst)
|
2015-05-27 09:40:19 +02:00
|
|
|
|
"Fold MPROC over LST and return a monadic value seeded by INIT.
|
|
|
|
|
|
|
|
|
|
(foldm %state-monad (lift2 cons %state-monad) '() '(a b c))
|
|
|
|
|
=> '(c b a) ;monadic
|
|
|
|
|
"
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(with-monad monad
|
|
|
|
|
(let loop ((lst lst)
|
|
|
|
|
(result init))
|
|
|
|
|
(match lst
|
|
|
|
|
(()
|
|
|
|
|
(return result))
|
2017-05-02 22:47:36 +02:00
|
|
|
|
((head . tail)
|
2015-05-27 09:40:19 +02:00
|
|
|
|
(>>= (mproc head result)
|
|
|
|
|
(lambda (result)
|
|
|
|
|
(loop tail result))))))))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
(define-template (mapm monad mproc lst)
|
2015-05-27 09:40:19 +02:00
|
|
|
|
"Map MPROC over LST and return a monadic list.
|
|
|
|
|
|
|
|
|
|
(mapm %state-monad (lift1 1+ %state-monad) '(0 1 2))
|
|
|
|
|
=> (1 2 3) ;monadic
|
|
|
|
|
"
|
2017-05-02 22:47:36 +02:00
|
|
|
|
;; XXX: We don't use 'foldm' because template specialization wouldn't work
|
|
|
|
|
;; in this context.
|
|
|
|
|
(with-monad monad
|
|
|
|
|
(let mapm ((lst lst)
|
|
|
|
|
(result '()))
|
|
|
|
|
(match lst
|
|
|
|
|
(()
|
|
|
|
|
(return (reverse result)))
|
|
|
|
|
((head . tail)
|
|
|
|
|
(>>= (mproc head)
|
|
|
|
|
(lambda (head)
|
|
|
|
|
(mapm tail (cons head result)))))))))
|
|
|
|
|
|
|
|
|
|
(define-template (sequence monad lst)
|
2013-10-03 22:45:25 +02:00
|
|
|
|
"Turn the list of monadic values LST into a monadic list of values, by
|
|
|
|
|
evaluating each item of LST in sequence."
|
|
|
|
|
(with-monad monad
|
2015-04-17 18:15:38 +02:00
|
|
|
|
(let seq ((lstx lst)
|
|
|
|
|
(result '()))
|
|
|
|
|
(match lstx
|
|
|
|
|
(()
|
|
|
|
|
(return (reverse result)))
|
|
|
|
|
((head . tail)
|
|
|
|
|
(>>= head
|
|
|
|
|
(lambda (item)
|
|
|
|
|
(seq tail (cons item result)))))))))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
2017-05-02 22:47:36 +02:00
|
|
|
|
(define-template (anym monad mproc lst)
|
2015-05-27 09:40:19 +02:00
|
|
|
|
"Apply MPROC to the list of values LST; return as a monadic value the first
|
|
|
|
|
value for which MPROC returns a true monadic value or #f. For example:
|
|
|
|
|
|
|
|
|
|
(anym %state-monad (lift1 odd? %state-monad) '(0 1 2))
|
|
|
|
|
=> #t ;monadic
|
|
|
|
|
"
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(with-monad monad
|
|
|
|
|
(let loop ((lst lst))
|
|
|
|
|
(match lst
|
|
|
|
|
(()
|
|
|
|
|
(return #f))
|
2017-05-02 22:47:36 +02:00
|
|
|
|
((head . tail)
|
2015-05-27 09:40:19 +02:00
|
|
|
|
(>>= (mproc head)
|
|
|
|
|
(lambda (result)
|
|
|
|
|
(if result
|
|
|
|
|
(return result)
|
|
|
|
|
(loop tail)))))))))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
|
|
|
|
(define-syntax listm
|
|
|
|
|
(lambda (s)
|
|
|
|
|
"Return a monadic list in MONAD from the monadic values MVAL."
|
|
|
|
|
(syntax-case s ()
|
|
|
|
|
((_ monad mval ...)
|
|
|
|
|
(with-syntax (((val ...) (generate-temporaries #'(mval ...))))
|
|
|
|
|
#'(mlet monad ((val mval) ...)
|
|
|
|
|
(return (list val ...))))))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Identity monad.
|
|
|
|
|
;;;
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(define-inlinable (identity-return value)
|
2013-10-03 22:45:25 +02:00
|
|
|
|
value)
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(define-inlinable (identity-bind mvalue mproc)
|
2013-10-03 22:45:25 +02:00
|
|
|
|
(mproc mvalue))
|
|
|
|
|
|
2013-10-02 21:58:19 +02:00
|
|
|
|
(define-monad %identity-monad
|
|
|
|
|
(bind identity-bind)
|
|
|
|
|
(return identity-return))
|
2013-10-03 22:45:25 +02:00
|
|
|
|
|
2015-01-17 18:46:41 +01:00
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; State monad.
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define-inlinable (state-return value)
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(values value state)))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (state-bind mvalue mproc)
|
|
|
|
|
"Bind MVALUE, a value in the state monad, and pass it to MPROC."
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(call-with-values
|
|
|
|
|
(lambda ()
|
|
|
|
|
(mvalue state))
|
|
|
|
|
(lambda (value state)
|
|
|
|
|
;; Note: as of Guile 2.0.11, declaring a variable to hold the result
|
|
|
|
|
;; of (mproc value) prevents a bit of unfolding/inlining.
|
|
|
|
|
((mproc value) state)))))
|
|
|
|
|
|
|
|
|
|
(define-monad %state-monad
|
|
|
|
|
(bind state-bind)
|
|
|
|
|
(return state-return))
|
|
|
|
|
|
|
|
|
|
(define* (run-with-state mval #:optional (state '()))
|
|
|
|
|
"Run monadic value MVAL starting with STATE as the initial state. Return
|
|
|
|
|
two values: the resulting value, and the resulting state."
|
|
|
|
|
(mval state))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (current-state)
|
|
|
|
|
"Return the current state as a monadic value."
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(values state state)))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (set-current-state value)
|
|
|
|
|
"Set the current state to VALUE and return the previous state as a monadic
|
|
|
|
|
value."
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(values state value)))
|
|
|
|
|
|
|
|
|
|
(define (state-pop)
|
|
|
|
|
"Pop a value from the current state and return it as a monadic value. The
|
|
|
|
|
state is assumed to be a list."
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(match state
|
|
|
|
|
((head . tail)
|
|
|
|
|
(values head tail)))))
|
|
|
|
|
|
|
|
|
|
(define (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."
|
|
|
|
|
(lambda (state)
|
|
|
|
|
(values state (cons value state))))
|
|
|
|
|
|
2013-10-03 22:45:25 +02:00
|
|
|
|
;;; monads.scm end here
|