2 寻找Self

在前一章中,我们构建了一个简单的对象系统。现在我们来考虑为点对象定义above方法,它读入的参数是另一个点,返回更高的(从y轴角度看)点:

  1. (method above (other-point)
  2. (if (> (-> other-point y?) y)
  3. other-point
  4. self))

请注意,我们直观地使用self来表示当前正在执行的对象;在其他有些语言中,它被称为this。显然,我们对OOP的描述并没有告诉我们self是什么。

2.1 Self是什么?

回过头看看最初那个对象的定义(没有宏的那个)。对象是函数;所以我们想要的是在这个函数范围内能够引用自己。该怎么做呢?研究递归的时候我们已经知道答案了!只需使用递归绑定(letrec)给函数—对象命名,然后就可以在方法定义中使用了:

  1. (define point
  2. (letrec ([self
  3. (let ([x 0])
  4. (let ([methods (list (cons 'x? (λ () x))
  5. (cons 'x! (nx)
  6. (set! x nx)
  7. self)))])
  8. (msg . args)
  9. (apply (cdr (assoc msg methods)) args))))])
  10. self))

请注意,letrec的主体就返回self,它绑定到我们定义的递归子程序。

  1. > ((point 'x! 10) 'x?)
  2. 10

在Smalltalk语言中,方法默认返回self。

请注意,赋值方法x!返回self,这使得我们可以链式传递消息。

2.2 用宏实现Self

在我们的OBJECT宏中使用上述模式:

  1. (defmac (OBJECT ([field fname init] ...)
  2. ([method mname args body] ...))
  3. #:keywords field method
  4. (letrec ([self
  5. (let ([fname init] ...)
  6. (let ([methods (list (cons 'mname (λ args body)) ...)])
  7. (λ (msg . vals)
  8. (apply (cdr (assoc msg methods)) vals))))])
  9. self))
  10. (defmac (-> o m arg ...)
  11. (o 'm arg ...))

用一些点对象试试:

  1. (define (make-point init-x)
  2. (OBJECT
  3. ([field x init-x])
  4. ([method x? () x]
  5. [method x! (nx) (set! x nx)]
  6. [method greater (other-point)
  7. (if (> (-> other-point x?) x)
  8. other-point
  9. self)])))
  10. > (let ([p1 (make-point 5)]
  11. [p2 (make-point 2)])
  12. (-> p1 greater p2))
  13. self: undefined;
  14. cannot reference undefined identifier

什么??我们明明用letrec定义了self,为什么报错说它没有定义呢?原因是——卫生!要知道Scheme的syntax-rules是卫生的,因此,它会透明地重命名宏引入的所有标识符,以确保在宏展开后他们不会意外绑定或者被绑定。使用DrRacket的宏步进器(macro stepper)可以很清楚地观察到这一点。你会看到,greater方法中的self标识符与letrec表达式中的同名标识符的颜色不同。

幸运的是,defmac支持一种方法,指定宏本身引入的标识符也可以被用户代码使用。这里我们唯一需要做的是指定self就是这样的标识符:

  1. (defmac (OBJECT ([field fname init] ...)
  2. ([method mname args body] ...))
  3. #:keywords field method
  4. #:captures self
  5. (letrec ([self
  6. (let ([fname init] ...)
  7. (let ([methods (list (cons 'mname (λ args body)) ...)])
  8. (λ (msg . vals)
  9. (apply (cdr (assoc msg methods)) vals))))])
  10. self))

2.3 用到Self的点对象

现在我们可以定义种种方法,或返回self,或在方法体中使用self:

  1. (define (make-point init-x init-y)
  2. (OBJECT
  3. ([field x init-x]
  4. [field y init-y])
  5. ([method x? () x]
  6. [method y? () y]
  7. [method x! (new-x) (set! x new-x)]
  8. [method y! (new-y) (set! y new-y)]
  9. [method above (other-point)
  10. (if (> (-> other-point y?) y)
  11. other-point
  12. self)]
  13. [method move (dx dy)
  14. (begin (-> self x! (+ dx (-> self x?)))
  15. (-> self y! (+ dy (-> self y?)))
  16. self)])))
  17. (define p1 (make-point 5 5))
  18. (define p2 (make-point 2 2))
  19. > (-> (-> p1 above p2) x?)
  20. 5
  21. > (-> (-> p1 move 1 1) x?)
  22. 6

2.4 互相递归的方法

上一节已经表明,方法可以通过向self发送消息来使用其他方法。这个例子展示相互递归的方法。

请在Java中尝试相同的定义,然后比较“大”数字的结果。是啊,我们的简单对象系统确实从尾调用优化中受益了!

  1. (define odd-even
  2. (OBJECT ()
  3. ([method even (n)
  4. (case n
  5. [(0) #t]
  6. [(1) #f]
  7. [else (-> self odd (- n 1))])]
  8. [method odd (n)
  9. (case n
  10. [(0) #f]
  11. [(1) #t]
  12. [else (-> self even (- n 1))])])))
  13. > (-> odd-even odd 15)
  14. #t
  15. > (-> odd-even odd 14)
  16. #f
  17. > (-> odd-even even 14)
  18. #t

我们现在的对象系统支持self,包括返回self、发送消息给self。请注意,方法中使用的self是在对象创建时被绑定的:在方法被定义时,它们捕获对self的绑定,此后该绑定就被固定了。我们将在下面的章节中看到,如果想要支持委托,或者想要支持类,这就行不通了。

2.5 嵌套的对象

对象和方法最终被编译成Scheme中的lambda,因此我们的对象继承了一些有趣的属性。首先,正如我们所看到的,它们是一等公民(不然这一切还有意思吗?)。另外,正如我们刚刚看到的,尾位置的方法调用被视为尾调用,因此空间没有浪费。接下来讨论另一个好处:我们可以使用高阶的编程模式,比如产生对象的对象(通常称为工厂)。换一种说法,运用合适的词法范围,我们可以定义嵌套的对象

考虑如下的例子:

  1. (define factory
  2. (OBJECT
  3. ([field factor 1]
  4. [field price 10])
  5. ([method factor! (nf) (set! factor nf)]
  6. [method price! (np) (set! price np)]
  7. [method make ()
  8. (OBJECT ([field price price])
  9. ([method val () (* price factor)]))])))
  10. > (define o1 (-> factory make))
  11. > (-> o1 val)
  12. 10
  13. > (-> factory factor! 2)
  14. > (-> o1 val)
  15. 20
  16. > (-> factory price! 20)
  17. > (-> o1 val)
  18. 20
  19. > (define o2 (-> factory make))
  20. > (-> o2 val)
  21. 40

在Java中你能这么做吗?

请验证这些返回。