当值是语法规则时,define vs define-syntax 和 let vs let-syntax 有什么区别?

What is the difference between define vs define-syntax and let vs let-syntax when value is syntax-rules?

我正在我的 Scheme 实现中实现 Hygienic 宏,我刚刚实现了 syntax-rules,但我有这段代码:

(define odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

那和这之间应该有什么区别:

(define-syntax odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

根据我的理解 syntax-rules 只是 return 语法转换器,为什么你不能只使用 define 将其分配给符号?为什么我需要使用 define-syntax?该表达式有什么额外的作用?

首先也要在scheme中工作?还是只有第二个?

还有 letlet-syntaxletrecletrec-syntax 之间的区别是什么。如果值是语法转换器,(define|let|letrec)-syntax 是否应该只进行类型检查?

编辑:

我有这个实现,仍然使用 lisp 宏:

;; -----------------------------------------------------------------------------
(define-macro (let-syntax vars . body)
  `(let ,vars
     ,@(map (lambda (rule)
              `(typecheck "let-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (letrec-syntax vars . body)
  `(letrec ,vars
     ,@(map (lambda (rule)
              `(typecheck "letrec-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (define-syntax name expr)
  (let ((expr-name (gensym)))
    `(define ,name
       (let ((,expr-name ,expr))
         (typecheck "define-syntax" ,expr-name "syntax")
         ,expr-name))))

这段代码正确吗?

此代码应该有效吗?

(let ((let (lambda (x) x)))
  (let-syntax ((odd? (syntax-rules ()
                        ((_ x) (not (even? x))))))
     (odd? 11)))

这个问题似乎暗示了对宏的一些深刻困惑。

让我们想象一种语言,其中 syntax-rules returns 一些语法转换器函数(我不确定这在 RnRS Scheme 中必须是真的,我认为在 Racket 中是真的),并且 letlet-syntax 是一样的。

所以让我们写这个函数:

(define (f v)
  (let ([g v])
    (g e (i 10)
       (if (= i 0)
           i
           (e (- i 1))))))

我们当然可以把它变成这样:

(define (f v n)
  (v e (i n)
     (if (<= i 0)
         i
         (e (- i 1)))))

另外我会告诉你,环境中没有ei的绑定。

解释器用这个定义做什么?它可以编译吗?它是否可以安全地推断出 i 不可能有任何意义,因为它被用作一个函数然后又被用作一个数字?它能安全地做任何事情吗?

答案是不,不能。在它知道函数的参数是什么之前,它什么也做不了。这意味着 每次调用 f 时,它都必须再次做出该决定 。特别是,v 可能是:

(syntax-rules ()
  [(_ name (var init) form ...)
   (letrec ([name (λ (var)
                    form ...)])
     (name init))]))

f 的定义确实有某种意义。

事情变得更糟:更糟。这个怎么样?

(define (f v1 v2 n)
  (let ([v v1])
    (v e (i n)
       ...
       (set! v (if (eq? v v1) v2 v1))
       ...)))

这意味着像这样的系统直到在解释代码的那一刻甚至在那之后才知道它要解释的代码是什么意思, 从上面第二个函数可以看出

因此,Lisp 并没有发生这种恐怖,而是做了一些理智的事情:他们将评估代码位的过程划分为 阶段 ,其中每个阶段在概念上发生在下一个阶段之前。

这是一些想象中的 Lisp 的序列(这有点接近 CL 所做的,因为我的大部分知识都是关于它的,但它并不打算代表任何特定的系统):

  1. 有一个阶段,代码可能在用户定义代码的帮助下从一些字符序列转换为某个对象;
  2. 有一个阶段,该对象被用户和系统定义的代码(宏)重写为其他对象——这个阶段的结果是用函数和少量原始特殊代码表示的东西传统上称为 'special forms' 的事物,在第 3 阶段和第 4 阶段的过程中已知;
  3. 可能有一个阶段编译来自阶段 2 的对象,并且该阶段可能涉及另一组用户定义的宏(编译器宏);
  4. 有一个计算结果代码的阶段。

并且对于每个代码单元,这些阶段按顺序发生每个阶段在下一个阶段开始之前完成

这意味着用户可以干预的每个阶段都需要自己的一组定义和绑定形式:例如需要可以说 'this thing controls what happens at phase 2'。

这就是 define-syntaxlet-syntax &c 所做的:他们说 'these bindings and definitions control what happens at phase 2'。例如,您不能使用 definelet 来执行此操作,因为 在阶段 2,这些操作还没有意义 :它们获得仅在第 3 阶段意味着(可能本身就是扩展为某些原始事物的宏)。在第 2 阶段,它们只是宏正在摄取和吐出的语法位。