Referentially transparent implementation for Guile
Lassi Kortela 17 Oct 2019 09:37 UTC
> several implementations that you provide in the git repository are
> based on `define-macro' (or similar constructs).
>
> The problem with `define-macro' is that it does not preserve
> referential transparency so the implementations are somewhat broken.
>
> For example, the Guile implementation breaks if `lambda*' is being
> rebound by a user of the `keyword-lambda' macro.
>
> A (hopefully) correct implementation of `keyword-lambda' for Guile,
> for example, would be:
>
> (define-syntax keyword-lambda
> (lambda (stx)
> (syntax-case stx ()
> ((_ (formals ... (keyword-symbols ...)) . body)
> #'(lambda* (formals ... #:key keyword-symbols ...) . body)))))
>
> (One may even rewrite this into a `syntax-rules' macro, which,
> however, isn't possible for the `keyword-call' macro in the Guile
> implementation; this has to be implemented in terms of `syntax-case'.)
Thank you very much for the close reading! You're right. There were so
many implementations that I didn't take the time to find an optimal
macro for each, figuring other people could help with that :)
Here's a syntax-rules/syntax-case based implementation for Guile. Is
this right?
----------------------------------------------------------------------
(use-modules (ice-9 optargs))
(define-syntax keyword-lambda
(syntax-rules ()
((_ (formals ... (keyword-symbols ...)) body ...)
(lambda* (formals ... #:key keyword-symbols ...) body ...))))
(define-syntax keyword-call
(lambda (stx)
(syntax-case stx ()
((_ kw-lambda positional-vals ... (keyword-syms-and-vals ...))
#`(kw-lambda
positional-vals ...
#,@(let loop ((alls #'(keyword-syms-and-vals ...)) (acc '()))
(cond ((null? alls) (reverse acc))
((null? (cdr alls))
(error "Missing keyword value in keyword-call"))
(else (let ((key (symbol->keyword
(syntax->datum (car alls))))
(val (cadr alls)))
(loop (cddr alls)
(cons val (cons key acc))))))))))))
----------------------------------------------------------------------
Before:
> (load "srfi/177-guile.scm")
> (let ((lambda* 'mischief)) (let ((foo (keyword-lambda (a b (c d))
(values a b c d)))) (keyword-call foo 1 2 (c 3))))
;;; <stdin>:2:38: warning: possibly unbound variable `a'
;;; <stdin>:2:38: warning: possibly unbound variable `b'
;;; <stdin>:2:38: warning: possibly unbound variable `c'
;;; <stdin>:2:38: warning: possibly unbound variable `d'
;;; <stdin>:2:66: warning: possibly unbound variable `a'
;;; <stdin>:2:66: warning: possibly unbound variable `b'
;;; <stdin>:2:66: warning: possibly unbound variable `c'
;;; <stdin>:2:66: warning: possibly unbound variable `d'
<unnamed port>:2:15: In procedure module-lookup: Unbound variable: a
After:
> (load "srfi/177-guile.scm")
> (let ((lambda* 'mischief)) (let ((foo (keyword-lambda (a b (c d))
(values a b c d)))) (keyword-call foo 1 2 (c 3))))
$1 = 1
$2 = 2
$3 = 3
$4 = #f