-
-
Save tfeb/0b8531c94cf685824626 to your computer and use it in GitHub Desktop.
#lang racket | |
(define (call/environment-variables thunk bindings) | |
;; This is a conventional call-with-whatever function: it calls thunk | |
;; with an environment augmented by bindings which is a list of pairs of | |
;; names and values | |
(let ([e (environment-variables-copy | |
(current-environment-variables))]) | |
(for ([b bindings]) | |
(match-let ([(list n v) b]) | |
(environment-variables-set! e n v))) | |
(parameterize ([current-environment-variables e]) | |
(thunk)))) | |
;;; My original overcomplicated version | |
;;; | |
;(define-syntax (with-environment-variables stx) | |
; ;; This is the macro. The only significant thing here is that I want everything | |
; ;; to be evaluated: both names and values of variable (I am not sure why), so | |
; ;; (w-e-v ([n v] ...) ...) -> (call/ev (λ () ...) (list (list n v) ...)) | |
; (syntax-case stx () | |
; [(_ () body ...) | |
; #'(call/environment-variables (λ () body ...) '())] | |
; [(_ (binding ...) body ...) | |
; #`(call/environment-variables | |
; (λ () body ...) | |
; (list #,@(for/list ([b (syntax->list #'(binding ...))]) | |
; (syntax-case b () | |
; [(n v) | |
; #'(list n v)]))))])) | |
;;; bmastenbrook's pretty version | |
;;; | |
;(define-syntax with-environment-variables | |
; (syntax-rules () | |
; [(_ ([name value] ...) body ...) | |
; (call/environment-variables | |
; (λ () body ...) (list (list name value) ...))])) | |
;;; My less-pretty but, I think, equivalent version | |
;;; | |
(define-syntax (with-environment-variables stx) | |
(syntax-case stx () | |
[(_ ([name value] ...) body ...) | |
#'(call/environment-variables | |
(λ () body ...) | |
(list (list name value) ...))])) | |
(module+ test | |
(require rackunit) | |
(let ([n #"FOO"] [v #"BAR"] [n2 #"FISH"] [v2 #"BAT"]) | |
(check-equal? | |
(call/environment-variables | |
(λ () (environment-variables-ref (current-environment-variables) n)) | |
`([,n ,v])) | |
v) | |
(check-equal? | |
(with-environment-variables ([n v]) | |
(environment-variables-ref (current-environment-variables) n)) | |
v) | |
(check-equal? | |
(with-environment-variables ([n v] [n2 v2]) | |
(let ([e (current-environment-variables)]) | |
(map (λ (n) (environment-variables-ref e n)) `(,n ,n2)))) | |
`(,v ,v2))) | |
(check-not-eq? | |
(with-environment-variables () (current-environment-variables)) | |
(current-environment-variables))) |
Thank you! I had not understood two things I think: that ...
is *
not +
, that it would work on compound expressions, and that the replacements could be hairy (ie it is smart enough to match ([name value] ...)
to (list (list name value) ...)
). Yes, that's three things: 'amongst our weapons are ...'.
The base case had also leaked in from an earlier macro, where I wanted the no-bindings case to transform into (begin ...)
rather than calling a slightly-expensive function: I can't do that here since the environment is mutable so I need to make a new one anyway.
I've pushed a new version of the gist with your version and a (less good) version of mine which does the same matching I think: I presume that these are equivalent.
Anyway, thanks again: I've learned a thing.
No problem; sometimes an example is worth a thousand documents. Your version is definitely equivalent to mine; the use of quasisyntax (the #` form) is the same as syntax (#') because you don't have any unquote forms, and syntax-rules is always trivially convertible to syntax-case:
(require (for-syntax (rename-in racket [syntax-rules racket:syntax-rules])))
(begin-for-syntax
(define-syntax syntax-rules
(racket:syntax-rules ()
[(_ literals (pattern expansion) ...)
(lambda (s)
(syntax-case s literals
(pattern #'expansion) ...))])))
Have you seen Greg Hendershott's "Fear Of Macros"? It might help if you're interested in continuing down the Racket macrology path: http://www.greghendershott.com/fear-of-macros/
Thanks: it was meant to be #'
: the backquote got in by mistake.
I should look at "Fear Of Macros": I was aware of it but had (probably incorrectly) assumed it was aimed at people who aren't used to the idea of Lisp-family macros at all, rather than people trying to grok Racket's in particular.
Hello Tim! Nice to see you on the Racket side of the world.
What you're doing is hygienic; generally speaking, you have to write something involving datum->syntax to actually break hygiene. But it's unnecessarily procedural - what you've written can be expressed very simply just using patterns:
No base case is required either; a ... pattern implicitly means "zero or more". Generally speaking, complex macros are still best written procedurally (and syntax-parse is a very nice tool for doing so), but when it's just a trivial pattern like this, I tend to write it in syntax-rules.