заменить один вызов функции другим с помощью макроса - PullRequest
0 голосов
/ 15 февраля 2019

Как заменить все вызовы функций на 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 голосов
/ 21 февраля 2019

Вы можете сделать это, определив собственную версию define, которая сохраняет выражение во время компиляции, которое replace-plus-with-mul может получить позже.

Два макроса define/replacable и replace-plus-with-mul имеютчтобы работать вместе, используя define-syntax и syntax-local-value:

  1. define/replacable использует define-syntax, чтобы связать compile-информация о времени с идентификатором, который он определяет.
  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, но только в этом.Это можно исправить, добавив структуру.

Second Pass, сохранив структуру, чтобы обычные пользователи также работали

В первой версии два макроса взаимодействовали следующим образом:

  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
0 голосов
/ 16 февраля 2019

Я не вижу простого способа точно получить то, что вы хотите работать, но с дополнительным ограничением это, безусловно, возможно.


Если вы согласны с ограничением, что вызов макросадолжен синтаксически содержать 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

В некотором смысле то, что вы хотите сделать, требует некоторой ленивой оценки, и это огромное семантическое изменение,Я не уверен, есть ли хороший способ сделать это, не «повреждая» другой код.

...