Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

replace one function call with another using a macro

Tags:

racket

How can I replace all function calls to f with function calls to g using a racket macro? I'm new to racket and I don't know how to process syntax objects, but I believe the use case I have in mind is something a racket macro could do. Consider the following example, where I want to replace plus with mul. The macro replace-plus-with-mul just returns current-seconds as a placeholder because I don't how what to do with the syntax object to replace plus with mul. Can a macro do this?

#lang racket

(define-syntax replace-plus-with-mul
  (lambda (stx) #'(current-seconds)))

(define plus (lambda (x y) (+ x y)))
(define mul (lambda (x y) (* x y)))

(define a 4)
(define b 2)
(define c (plus a b))
(replace-plus-with-mul d c) ;; (define d (mul a b))
(print d) ;; should print 8
like image 935
Mei Zhang Avatar asked Feb 15 '19 20:02

Mei Zhang


1 Answers

I don't see an easy way to precisely get what you want to work, but with an additional restriction, it's certainly possible.


If you are OK with the restriction that the macro invocation must syntactically contain plus, then simply recursively replace all plus with mul inside the macro

;; main.rkt
#lang racket

(define plus (lambda (x y) (+ x y)))
(define mul (lambda (x y) (* x y)))

(define-for-syntax (replace stx)
  (syntax-case stx ()
    [(a . b)
     (datum->syntax stx (cons (replace #'a)
                              (replace #'b)))]
    [_
     (and (identifier? stx)
          (free-identifier=? #'plus stx))
     #'mul]
    ;; FIXME: need more cases (like box or vector), but 
    ;; this is sufficient for the demo
    [_ stx]))

(define-syntax (replace-plus-with-mul stx)
  (syntax-case stx ()
    [(_ id expr)
     #`(define id
         #,(replace (local-expand #'expr 'expression '())))]))

(replace-plus-with-mul c (plus 3 (let ([plus 10]) plus)))
c                               ; prints 30
(plus 3 (let ([plus 10]) plus)) ; prints 13

If you are OK with the restriction that plus that you would like to change must not already be used, like the below code:

(define (c) (plus 3 2))
(replace-plus-with-mul d (c))

Then there are several approaches to this. One is to override #%module-begin to replace all plus to (if (current-should-use-mul?) mul plus) and expand replace-plus-with-mul to (parameterize ([current-should-use-mul? #t]) ...). Here's the full code:

;; raquet.rkt
#lang racket

(provide (except-out (all-from-out racket)
                     #%module-begin)
         (rename-out [@module-begin #%module-begin])
         plus
         mul
         replace-plus-with-mul)

(define plus (lambda (x y) (+ x y)))
(define mul (lambda (x y) (* x y)))
(define current-should-use-mul? (make-parameter #f))

(define-for-syntax (replace stx)
  (syntax-case stx ()
    [(a . b)
     (datum->syntax stx (cons (replace #'a)
                              (replace #'b)))]
    [_
     (and (identifier? stx)
          (free-identifier=? #'plus stx))
     #'(if (current-should-use-mul?) mul plus)]
    ;; FIXME: need more cases (like box or vector), but 
    ;; this is sufficient for the demo
    [_ stx]))

(define-syntax (@module-begin stx)
  (syntax-case stx ()
    [(_ form ...)
     #'(#%module-begin (wrap-form form) ...)]))

(define-syntax (wrap-form stx)
  (syntax-case stx ()
    [(_ form) (replace (local-expand #'form 'top-level '()))]))

(define (activate f)
  (parameterize ([current-should-use-mul? #t])
    (f)))

(define-syntax (replace-plus-with-mul stx)
  (syntax-case stx ()
    [(_ id expr)
     #`(define id (activate (lambda () expr)))]))

and

;; main.rkt
#lang s-exp "raquet.rkt"

(define (c) (plus 3 (let ([plus 10]) plus)))
(replace-plus-with-mul a (c))
a    ; prints 30
(c)  ; prints 13

In a sense, what you want to do needs a kind of lazy evaluation, and this is a huge semantic change. I'm not sure if there's a good way to do it while not "damaging" other code.

like image 62
Sorawee Porncharoenwase Avatar answered Sep 22 '22 00:09

Sorawee Porncharoenwase