2
votes

I'm currently learning how to write CL style macros (define-macro) in Scheme. As a simple example, I wrote a struct macro that defines functions like make-thing, thing?, thing-field accessors and so on.

Now I'd like to combine multiple defines in a single macro, but only the last one is actually used. Currently I'm using eval to define the functions globally (?), but there must be some better way... any ideas?

The code so far:

;(use-modules (ice-9 pretty-print))

(define-macro (struct name key table fields)
  (for-each
    (lambda (field)
      (eval
        `(define ,(string->symbol (string-append (symbol->string name) "-" (symbol->string field)))
          (lambda (x)
            (if (,(string->symbol (string-append (symbol->string name) "?")) x)
              (cadr (assq (quote ,field) (cdr x)))
              #f)))
        (interaction-environment)))
      fields)
  (eval
    `(define ,(string->symbol (string-append (symbol->string name) "?"))
       (lambda (x)
         (and
           (list? x)
           (eq? (car x) (quote ,name))
           ,@(map (lambda (field) `(assq (quote ,field) (cdr x))) fields)
           #t)))
    (interaction-environment))
  (eval
    `(define ,(string->symbol (string-append "make-" (symbol->string name)))
       (lambda ,fields
         (list (quote ,name)
               ,@(map (lambda (field) `(list (quote ,field) ,field)) fields))))
    (interaction-environment))
  (eval
    `(define ,(string->symbol (string-append "save-" (symbol->string name)))
       (lambda (x)
         (if (,(string->symbol (string-append (symbol->string name) "?")) x)
           (call-with-output-file ; TODO: In PLT mit zusaetzlichem Parameter #:exists 'replace
             (string-append "data/" ,(symbol->string table) "/"
                            (,(string->symbol (string-append (symbol->string name) "-" (symbol->string key))) x))
             (lambda (out) (write x out)))
           #f)))
    (interaction-environment))
  `(define ,(string->symbol (string-append "get-" (symbol->string name)))
     (lambda (id)
       (let ((ret (call-with-input-file (string-append "data/" ,(symbol->string table) "/" id) read)))
         (if (,(string->symbol (string-append (symbol->string name) "?")) ret)
           ret
           #f))))
; TODO: (define (list-customers . search-words) ...)
  )

(struct customer id customers (id name name_invoice address_invoice zip_invoice city_invoice state_invoice))
;(pretty-print (macroexpand '(struct customer id customers (id name name_invoice address_invoice zip_invoice city_invoice state_invoice))))
;(newline)

(define c (make-customer "C-1001" "Doe, John" "John Doe" "Some-Street" "Some-Zip" "Some-City" "Germany"))
(write c)
(newline)
(write (customer-id c))
(newline)
(write (customer-name c))
(newline)
(save-customer c)
(write (get-customer "C-1001"))
(newline)
1
Note that Guile has proper hygienic macros, so using them would be a much better idea than using define-macro. (You can probably get help about this on the guile mailing list.) - Eli Barzilay
@Eli: That's correct, but the point of my exercise deliberately was to write CL style macros. I'm still not sure whether to pursue Scheme or CL, so I'm learning "portable" skills first... - user434817
that makes sense only in a very superficial and shallow way. Hygienic macros are different enough from symbolic defmacros, that it's similar to defmacro vs CPP macros. - Eli Barzilay
@Eli -- That's why I want to learn CL style defmacros first: They are in CL and Scheme. I have yet to choose which LISP to concentrate on. In the long run, I will learn both of them, and Clojure as well. But for now, defmacros allow me to switch to CL and back with little effort, so I'm learning them first. - user434817

1 Answers

2
votes

You don't need eval here; use begin instead to group those definitions together into a list; i.e., the template to be expanded should be of the form:

`(begin 
   ,@(map ...)
   (define ...)
   (define ...)
   ...)

Edit:

Change for-each to map as suggested by OP.