[racket] an "outer" macro toy
Thought this might amuse other folks; here is a sketch of what it can
look like to intentionally break lexical scoping in a controlled way.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket
;; Here's an implementation of an "outer" form that allows one to get
at the binding
;; outside of a current function definition. For this to work, we
need special cooperation
;; from the function-building syntax.
(require racket/stxparam)
;; We keep track of the currently-expanding function.
(define-syntax-parameter current-scoped-fun #f)
;; The function form "fun" sets up a compile-time parameter that holds onto the
;; function.
(define-syntax (fun stx)
(syntax-case stx ()
[(_ ids body ...)
(with-syntax ([fun-stx stx])
(syntax/loc stx
(syntax-parameterize ([current-scoped-fun #'fun-stx])
(lambda ids body ...))))]))
;; Finally, the "outer" form can use the current-scoped-fun to
;; create a new syntax object that takes its lexical information from
;; the entry of the function.
(define-syntax (outer stx)
(syntax-case stx ()
[(_ id)
(identifier? #'id)
(datum->syntax (syntax-parameter-value #'current-scoped-fun)
(syntax-e #'id)
stx)]))
;; For example:
(define x 42)
(define g (fun (x)
(displayln x)
(displayln (outer x))))
;; When we call:
(g 17)
;; we'll see the normal lexical binding on the first line (17),
;; followed by the value of the outer binding on the second line (42).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
To make "outer" work more like a declaration, where it affects the
rest of the scope of a function body, we can use an internal
define-syntax, and then things look even more hilarious:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket
;; Second toy "outer":
(require racket/stxparam)
(define-syntax-parameter current-scoped-fun #f)
(define-syntax (fun stx)
(syntax-case stx ()
[(_ ids body ...)
(with-syntax ([fun-stx stx])
(syntax/loc stx
(syntax-parameterize ([current-scoped-fun #'fun-stx])
(lambda ids body ...))))]))
(define-syntax (outer stx)
(syntax-case stx ()
[(_ id)
(identifier? #'id)
(with-syntax ([outered-id
(datum->syntax (syntax-parameter-value #'current-scoped-fun)
(syntax-e #'id)
stx)])
#'(define-syntax id
(make-rename-transformer #'outered-id)))]))
;; For example:
(define x 42)
(define g (fun ()
(let ([x 16])
(displayln x)
(let ()
(outer x)
(displayln x))
(displayln x))))
(g)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Anyway, just wanted to throw that out, since it helped me to
understand the role of the first argument to datum->syntax.