-
-
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))) |
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:
(define-syntax with-environment-variables
(syntax-rules ()
((_ ([name value] ...) body ...)
(call/environment-variables
(lambda () body ...)
(list (list name value) ...)))))
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.
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.
I'm a CL person, feeling my way around Racket's macro system. This works, but I'm wondering if