使用宏将一个函数调用替换为另一个函数

时间:2019-02-15 20:55:30

标签: racket

如何使用球拍宏将对f的所有函数调用替换为对g的函数调用?我是球拍的新手,我不知道如何处理语法对象,但是我相信我想到的用例是球拍宏可以做的事情。考虑以下示例,其中我想用plus替换mul。宏replace-plus-with-mul仅返回current-seconds作为占位符,因为我不知道如何用plus替换mul的语法对象。宏可以这样做吗?

#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

2 个答案:

答案 0 :(得分:4)

我没有找到一种简单的方法来精确地获得您想要的工作,但是有一个额外的限制,这肯定是可能的。


如果您对宏调用必须在语法上包含plus的限制感到满意,那么只需用宏内的plus递归替换所有mul

;; 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

如果您对要更改的plus的限制一定没有被使用感到满意,例如以下代码:

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

然后有几种解决方法。一种是覆盖#%module-begin,将所有plus替换为(if (current-should-use-mul?) mul plus),然后将replace-plus-with-mul扩展为(parameterize ([current-should-use-mul? #t]) ...)。这是完整的代码:

;; 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)))]))

;; 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

从某种意义上说,您想要做的事情需要某种懒惰的评估,这是一个巨大的语义变化。我不确定在没有“破坏”其他代码的情况下是否有一个很好的方法。

答案 1 :(得分:2)

您可以通过定义自己的define版本来实现此目的,该版本可以保存编译时的表达式,replace-plus-with-mul以后可以获取。

两个宏define/replacablereplace-plus-with-mul必须使用define-syntaxsyntax-local-value一起工作:

  1. define/replacable使用define-syntax将编译时信息与其定义的标识符相关联。
  2. replace-plus-with-mul使用syntax-local-value查找该编译时信息。

初次通过,直接在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))])

使用这些定义,该程序可以工作:

(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

但是,本示例中的c不能用作正则表达式。它可以在replace-plus-with-mul中使用,但只能在其中使用。可以通过添加结构来解决此问题。

第二遍,保存结构以便正常使用也可以

在第一个版本中,两个宏的通信方式如下:

  1. define/replacable使用define-syntax将编译时信息与其定义的标识符相关联。
  2. replace-plus-with-mul使用syntax-local-value查找该编译时信息。

但是,这不允许标识符具有正常行为。为此,我们需要这样的东西:

  1. define/replacable使用define-syntax将其定义的标识符与包含两个的编译时结构相关联:
    • 正常行为
    • 替换行为
  2. replace-plus-with-mul使用syntax-local-value查找该编译时结构,并从中获取replace行为
  3. 普通的Racket宏扩展程序使用syntax-local-value查找该编译时结构,并将其用作过程以用作宏。因此,我们应该使该结构具有正常行为的#:property prop:procedure

此结构可以如下所示:

(begin-for-syntax
  ;; normal : Expression -> Expression
  ;; replace : Identifier Identifier -> Expression
  (struct replacable-id [normal replace]
    #:property prop:procedure (struct-field-index normal)))

现在define/replacable宏应生成一个define-syntax来构造其中之一:

(define-syntax name
  (replacable-id ???
                 (lambda (plus mul)
                   ...what-we-had-before...)))

如果我们希望正常行为看起来像一个变量,则可以使用???中的make-variable-like-transformer来填充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))))

然后define/replacable可以生成如下内容:

(define normal-name expr)
(define-syntax name
  (replacable-id (make-var-like-transformer #'normal-name)
                 (lambda (plus mul)
                   ...what-we-had-before...)))

将它们放在一起:

#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))])

并尝试一下:

(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