【问题标题】:replace one function call with another using a macro使用宏将一个函数调用替换为另一个函数调用
【发布时间】:2019-02-15 20:55:30
【问题描述】:

如何使用球拍宏将所有对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

【问题讨论】:

  • 如果还要重新定义define可以吗?
  • 我实际上也有兴趣看到重新定义define 的解决方案,或者如果它涉及在我的示例中使用一些custom-define 而不是define,那也很有趣。
  • 好的。我会在今晚的某个时候尝试发布,因为您有兴趣。
  • 好的,我已经发布了一个描述两个宏的答案,一个自定义定义define/replacablereplace-plus-with-mul,它们使用define-syntaxsyntax-local-value 进行通信

标签: racket


【解决方案1】:

我没有看到一种简单的方法来精确地得到你想要的工作,但是有一个额外的限制,这当然是可能的。


如果您对宏调用必须在语法上包含 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

从某种意义上说,你想做的事情需要一种惰性求值,这是一个巨大的语义变化。我不确定是否有一种好的方法可以做到这一点,同时又不会“破坏”其他代码。

【讨论】:

    【解决方案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 查找编译时信息。

    First Pass,直接在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...)))
    

    如果我们希望正常行为看起来像一个变量,我们可以使用来自syntax/transformermake-variable-like-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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-19
      • 2021-08-24
      • 1970-01-01
      • 2019-06-20
      • 1970-01-01
      • 2017-03-07
      相关资源
      最近更新 更多