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) => 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
.
1 Answer 1
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) => 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!") ; => "Hello, world!"
(let-with
((alist (first middle last)))
(string-append first " " middle ". " last)) ; => "Jane Q. Doe"
(let-with
((alist (first))
(blist (middle last)))
(string-append first " " middle ". " last)) ; => "Jane R. Lee"
(let-with
((alist (first))
(blist (middle))
(clist (last)))
(string-append first " " middle ". " last)) ; => "Jane R. Paz"
(let-with
((first (title))
(alist (first))
(blist (middle))
(clist (last))
(first (suffix)))
(string-append first " " middle ". " last)) ; => "Jane R. Paz"
(let-with
((alist (first middle last)))
(set! first "Rose")
(string-append first " " middle ". " last)) ; => "Rose Q. Doe"
(let-with
((alist (first middle last)))
(string-append first " " middle ". " last)) ; => "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.
-
\$\begingroup\$ Thanks for your answer---I haven't forgotten about you (I've read it the moment it was posted). I wanted to write a full response to that, with my ideas about how such an extended macro might be implemented, etc., but my free time is so very limited at the moment. :-( \$\endgroup\$C. K. Young– C. K. Young2011年04月27日 12:27:20 +00:00Commented Apr 27, 2011 at 12:27