You can do this by defining your own version of define
, which saves the expression at compile time, which replace-plus-with-mul
can get later.
The two macros define/replacable
and replace-plus-with-mul
have to work together using define-syntax
and syntax-local-value
:
define/replacable
uses define-syntax
to associate compile-time information with the identifier it defines.
replace-plus-with-mul
uses syntax-local-value
to look up that compile-time information.
First Pass, saving a function directly in the define-syntax
#lang racket
(require syntax/parse/define
(for-syntax syntax/transformer))
(define-syntax-parser define/replacable
[(_ name:id expr:expr)
#:with plus (datum->syntax #'name 'plus)
#:with mul (datum->syntax #'name 'mul)
#'(define-syntax name
;; Identifier Identifier -> Expression
;; Replaces plus and mul within the expr
;; with the two new identifiers passed to
;; the function
(lambda (plus mul)
(with-syntax ([plus plus] [mul mul])
#'expr)))])
(define-syntax-parser replace-plus-with-mul
[(_ name:id replacable:id)
(define replace (syntax-local-value #'replacable))
#`(define name #,(replace #'mul #'mul))])
With these definitions, this program works:
(define plus (lambda (x y) (+ x y)))
(define mul (lambda (x y) (* x y)))
(define a 4)
(define b 2)
(define/replacable c (plus a b))
(replace-plus-with-mul d c) ;; (define d (mul a b))
(print d)
;=output> 8
However, c
in this example cannot be used as a normal expression. It can be used within replace-plus-with-mul
, but only within that. This can be fixed by adding a struct.
Second Pass, saving a struct so that normal uses also work
In the first version, the two macros communicated like this:
define/replacable
uses define-syntax
to associate compile-time information with the identifier it defines.
replace-plus-with-mul
uses syntax-local-value
to look up that compile-time information.
However, this doesn't allow the identifiers to have normal behavior. To do that we need something like this:
define/replacable
uses define-syntax
to associate the identifier it defines with a compile-time struct which contains both:
- normal behavior
- replace behavior
replace-plus-with-mul
uses syntax-local-value
to look up that compile-time struct, and get the replace
behavior out of it
- The normal Racket macro expander uses
syntax-local-value
to look up that compile-time struct, and uses it as a procedure to apply as a macro. Because of this, we should make the struct have #:property prop:procedure
with the normal behavior.
This struct can look like this:
(begin-for-syntax
;; normal : Expression -> Expression
;; replace : Identifier Identifier -> Expression
(struct replacable-id [normal replace]
#:property prop:procedure (struct-field-index normal)))
Now the define/replacable
macro should generate a define-syntax
that constructs one of those:
(define-syntax name
(replacable-id ???
(lambda (plus mul)
...what-we-had-before...)))
If we want the normal behavior to look like a variable, we can fill in the ???
hole using make-variable-like-transformer
from syntax/transformer
:
(require (for-syntax syntax/transformer))
(begin-for-syntax
;; Identifier -> [Expression -> Expression]
(define (make-var-like-transformer id)
(set!-transformer-procedure (make-variable-like-transformer id))))
Then define/replacable
can generate something like this:
(define normal-name expr)
(define-syntax name
(replacable-id (make-var-like-transformer #'normal-name)
(lambda (plus mul)
...what-we-had-before...)))
Putting it all together:
#lang racket
(require syntax/parse/define
(for-syntax syntax/transformer))
(begin-for-syntax
;; Identifier -> [Expression -> Expression]
(define (make-var-like-transformer id)
(set!-transformer-procedure (make-variable-like-transformer id)))
;; normal : Expression -> Expression
;; replace : Identifier Identifier -> Expression
(struct replacable-id [normal replace]
#:property prop:procedure (struct-field-index normal)))
(define-syntax-parser define/replacable
[(_ name:id expr:expr)
#:with plus (datum->syntax #'name 'plus)
#:with mul (datum->syntax #'name 'mul)
#'(begin
(define normal-name expr)
(define-syntax name
(replacable-id (make-var-like-transformer #'normal-name)
(lambda (plus mul)
(with-syntax ([plus plus] [mul mul])
#'expr)))))])
(define-syntax-parser replace-plus-with-mul
[(_ name:id replacable:id)
(define value (syntax-local-value #'replacable))
(define replace (replacable-id-replace value))
#`(define name #,(replace #'mul #'mul))])
And trying it out:
(define plus (lambda (x y) (+ x y)))
(define mul (lambda (x y) (* x y)))
(define/replacable a 4)
(define/replacable b 2)
(define/replacable c (plus a b))
(replace-plus-with-mul d c) ;; (define d (mul a b))
(print d)
;=output> 8
define
? – Devoiddefine
, or if it involves using somecustom-define
instead ofdefine
in my example, that'd also be interesting. – Kittrelldefine/replacable
andreplace-plus-with-mul
, which communicate usingdefine-syntax
andsyntax-local-value
– Devoid