Is this a good way to implement let-with? I've implemented a let-with macro that takes an association list, keyed by symbols, that enables code to bind their corresponding values to local variables.

It's designed to have a similar effect to JavaScript's with, but without with's attendant problems (by making you explicitly list the variables to bind).



(define-syntax let-with
  (syntax-rules ()
    ((let-with alist (key ...) expr ...)
     (let ((key (cond ((assq 'key alist) =&gt; cdr))) ...) expr ...))))


I'd love to hear of any improvements to let-with's interface and/or implementation.

Example usage:



(define alist '((first . "Jane")
                (last . "Doe")))
(let-with alist (first last)
  (string-append first " " last))    ; "Jane Doe"


Keys that don't exist in the incoming alist receive an undefined value; they do not fail to get bound, unlike JavaScript's with.
JavaScript's with statement allows one to modify object members.  This may not be possible for certain types of values (such as strings) using the let-with macro as defined above.



One could generalize let-with to bind values from several association lists.  It could have the form:

(let-with
    ((alist0 (key00 key01 key02...))
     (alist1 (key10 key11 key12...)))
  expr0
  expr1
  ...)


Aside from being more general, the above form closely resembles the let form.  I prefer this over the simpler form, though I would understand if your taste and needs differ.

Here's my implementation of the general form--it uses two auxiliary macros, one to generate two lists (alists and their corresponding keys) and the other to emit the actual code (which looks almost exactly like your definition above):

(define-syntax let-with
  (syntax-rules ()
    ((let-with bindings . body)
     (gen-let-with bindings () () body))))

(define-syntax gen-let-with
  (syntax-rules ()
    ((gen-let-with () alists keys body)
     (emit-let-with-code alists keys body))
    ((gen-let-with ((alist (key)) . rest-bindings) alists keys body)
     (gen-let-with
      rest-bindings
      (alist . alists)
      (key . keys)
      body))
    ((gen-let-with ((alist (key . rest-keys)) . rest-bindings) alists keys body)
     (gen-let-with
      ((alist rest-keys) . rest-bindings)
      (alist . alists)
      (key . keys)
      body))))

(define-syntax emit-let-with-code
  (syntax-rules ()
    ((emit-let-with-code (alist ...) (key ...) (expr ...))
     (let ((key (cond ((assq 'key alist) =&gt; cdr))) ...) expr ...))))


Perhaps there is a simpler definition.  I haven't played with macros in a while. =)



Here are some examples:

(define alist '((first . "Jane") (middle . "Q") (last . "Doe")))
(define blist '((first . "John") (middle . "R") (last . "Lee")))
(define clist '((first . "Jose") (middle . "S") (last . "Paz")))
(define first '((title . "Ms") (suffix . "Esq")))

(let-with
    ()
  "Hello, world!")    ;    =&gt; "Hello, world!"

(let-with
    ((alist (first middle last)))
  (string-append first " " middle ". " last))    ;    =&gt; "Jane Q. Doe"

(let-with
    ((alist (first))
     (blist (middle last)))
  (string-append first " " middle ". " last))    ;    =&gt; "Jane R. Lee"

(let-with
    ((alist (first))
     (blist (middle))
     (clist (last)))
  (string-append first " " middle ". " last))    ;    =&gt; "Jane R. Paz"

(let-with
    ((first (title))
     (alist (first))
     (blist (middle))
     (clist (last))
     (first (suffix)))
  (string-append first " " middle ". " last))    ;    =&gt; "Jane R. Paz"

(let-with
    ((alist (first middle last)))
  (set! first "Rose")
  (string-append first " " middle ". " last))    ;    =&gt; "Rose Q. Doe"

(let-with
    ((alist (first middle last)))
  (string-append first " " middle ". " last))    ;    =&gt; "Jane Q. Doe"


Since let-with expands into a single let, the third-to-last example works.

The last two examples demonstrate the inability to modify a value in its corresponding association list.