SICP:可以或者在lisp中定义为没有gensym的语法转换?

时间:2013-08-17 02:46:13

标签: macros lisp scheme sicp

我试图解决计算机编程结构和解释问题4.4的最后部分;任务是将实现为语法转换。只定义了基本的句法形式;引用,if,begin,cond,define,apply和lambda。

(或b ... c)等于第一个真值,如果没有值,则为false。

希望接近它的方式是将例如(或b c)转换为

(if a a (if b b (if c c false)))

这个问题是a,b和c将被评估两次,如果它们中的任何一个有副作用,可能会给出不正确的结果。所以我想要像let

这样的东西
(let ((syma a)) 
     (if syma syma (let ((symb b)) 
                        (if symb symb (let ((symc c))
                                           (if (symc symc false)) )) )) )

这反过来可以通过lambda实现,如练习4.6中所示。现在的问题是确定符号syma,symb和symc;例如,如果表达式b包含对变量syma的引用,那么let将破坏绑定。因此,我们必须让syma不是b或c中的符号。

现在我们遇到了障碍;我能看出这个漏洞的唯一方法就是将不能的符号放在传递给eval的任何表达式中。 (这包括可能已被其他语法转换传入的符号)。

但是因为我没有直接访问表达式的环境,所以我不确定是否有合理的方法来生成这样的符号;我认为Common Lisp具有用于此目的的函数gensym(这意味着在metacircular解释器中保持状态,危及任何并发使用)。

我错过了什么吗?有没有办法实现或不使用gensym?我知道Scheme有它自己的hygenic宏系统,但我还没有弄清楚它是如何工作的,我不确定它是否在它下面有一个gensym。

2 个答案:

答案 0 :(得分:6)

我认为你可能想要做的是转换为语法扩展,其中各种形式的评估不是嵌套的。您可以这样做,例如,将每个表单包装为lambda函数,然后您正在使用的方法就可以了。例如,你可以做像

这样的事情
(or a b c)

进入

(let ((l1 (lambda () a))
      (l2 (lambda () b))
      (l3 (lambda () c)))
  (let ((v1 (l1)))
    (if v1 v1
      (let ((v2 (l2)))
        (if v2 v2
          (let ((v3 (l3)))
            (if v3 v3
              false)))))))

(实际上,lambda函数调用的评估仍然嵌套在iflet中,但是lambda的定义1}}函数位于一个位置,以便在嵌套的iflet中调用它们不会对捕获的绑定造成任何困难。)这不解决的问题你如何获得变量l1 - l3v1 - v3,但这并不重要,它们都不属于lambda函数的主体,因此您无需担心它们是否出现在体内。实际上,您可以将相同的变量用于所有结果:

(let ((l1 (lambda () a))
      (l2 (lambda () b))
      (l3 (lambda () c)))
  (let ((v (l1)))
    (if v v
      (let ((v (l2)))
        (if v v
          (let ((v (l3)))
            (if v v
              false)))))))

此时,你真的只是在循环展开一个更通用的形式,如:

(define (functional-or . functions)
  (if (null? functions)
      false
      (let ((v ((first functions))))
        (if v v
            (functional-or (rest functions))))))

并且(or a b c)的扩展只是

(functional-or (lambda () a) (lambda () b) (lambda () c))

此方法也用于an answerWhy (apply and '(1 2 3)) doesn't work while (and 1 2 3) works in R5RS?。这一切都不需要任何GENSYM

答案 1 :(得分:1)

在SICP中,您将获得两种实施方式。将它们作为特殊形式处理的一个,这些形式是微不足道的,一个是派生表达式。我不确定他们是否真的认为你会认为这是一个问题,但你可以通过实施gensym或改变variable?以及如何制作这样的派生变量来实现:

;; a unique tag to identify special variables
(define id (vector 'id))

;; a way to make such variable
(define (make-var x)
  (list id x))

;; redefine variable? to handle macro-variables
(define (variable? exp) 
  (or (symbol? exp)
      (tagged-list? exp id)))


;; makes combinations so that you don't evaluate
;; every part twice in case of side effects (set!)
(define (or->combination terms)
  (if (null? terms)
      'false
      (let ((tmp (make-var 'tmp)))
        (list (make-lambda (list tmp)
                           (list (make-if tmp 
                                    tmp 
                                    (or->combination (cdr terms)))))
              (car terms)))))


;; My original version
;; This might not be good since it uses backquotes not introduced 
;; until chapter 5 and uses features from exercise 4.6
;; Though, might be easier to read for some so I'll leave it.
(define (or->combination terms)
  (if (null? terms)
      'false
      (let ((tmp (make-var 'tmp)))
        `(let ((,tmp ,(car terms)))
           (if ,tmp
               ,tmp
               ,(or->combination (cdr terms)))))))

它的工作原理是make-var每次调用时都会创建一个新列表,即使使用相同的参数也是如此。因为它有id,因为它的第一个元素variable?会将其标识为变量。由于它是一个列表,如果列表相同,它只会在变量查找中与eq?匹配,因此lookup-variable-value所有几个嵌套的or->组合tmp-vars都会被视为不同(eq? (list) (list)) => #f 1}}和特殊变量是列表,它们永远不会影响代码中的任何符号。

这受到Al Petrofsky的eiod的影响,它以类似的方式实现语法规则。除非你将其他实现视为剧透,否则你应该给它一个阅读。