define-macro Marc Nieper-Wißkirchen (17 Oct 2019 08:52 UTC)
Referentially transparent implementation for Guile Lassi Kortela (17 Oct 2019 09:37 UTC)

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