2 寻找Self

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

(method above (other-point)
        (if (> (-> other-point y?) y)
            other-point
            self))

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

2.1 Self是什么?

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

(define point
  (letrec ([self
            (let ([x 0])
              (let ([methods (list (cons 'x?  (λ () x))
                                   (cons 'x! (λ (nx)
                                               (set! x nx)
                                               self)))])
                (λ (msg . args)
                  (apply (cdr (assoc msg methods)) args))))])
    self))

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

> ((point 'x! 10) 'x?)
10

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

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

2.2 用宏实现Self

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

(defmac (OBJECT ([field fname init] ...)
                ([method mname args body] ...))
  #:keywords field method
  (letrec ([self
            (let ([fname init] ...)
              (let ([methods (list (cons 'mname (λ args body)) ...)])
                (λ (msg . vals)
                  (apply (cdr (assoc msg methods)) vals))))])
    self))

(defmac (-> o m arg ...)
  (o 'm arg ...))

用一些点对象试试:

(define (make-point init-x)
  (OBJECT
   ([field x init-x])
   ([method x? () x]
    [method x! (nx) (set! x nx)]
    [method greater (other-point)
            (if (> (-> other-point x?) x)
                other-point
                self)])))

> (let ([p1 (make-point 5)]
        [p2 (make-point 2)])
    (-> p1 greater p2))
self: undefined;
 cannot reference undefined identifier

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

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

(defmac (OBJECT ([field fname init] ...)
                ([method mname args body] ...))
  #:keywords field method
  #:captures self
  (letrec ([self
            (let ([fname init] ...)
              (let ([methods (list (cons 'mname (λ args body)) ...)])
                (λ (msg . vals)
                  (apply (cdr (assoc msg methods)) vals))))])
    self))

2.3 用到Self的点对象

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

(define (make-point init-x init-y)
 (OBJECT
  ([field x init-x]
   [field y init-y])
  ([method x? () x]
   [method y? () y]
   [method x! (new-x) (set! x new-x)]
   [method y! (new-y) (set! y new-y)]
   [method above (other-point)
           (if (> (-> other-point y?) y)
               other-point
               self)]

   [method move (dx dy)
           (begin (-> self x! (+ dx (-> self x?)))
                  (-> self y! (+ dy (-> self y?)))
                  self)])))

(define p1 (make-point 5 5))
(define p2 (make-point 2 2))

> (-> (-> p1 above p2) x?)
5
> (-> (-> p1 move 1 1) x?)
6

2.4 互相递归的方法

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

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

(define odd-even
  (OBJECT ()
   ([method even (n)
            (case n
              [(0) #t]
              [(1) #f]
              [else (-> self odd (- n 1))])]
    [method odd (n)
            (case n
              [(0) #f]
              [(1) #t]
              [else (-> self even (- n 1))])])))


> (-> odd-even odd 15)
#t
> (-> odd-even odd 14)
#f
> (-> odd-even even 14)
#t

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

2.5 嵌套的对象

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

考虑如下的例子:

(define factory
  (OBJECT
   ([field factor 1]
    [field price 10])
   ([method factor! (nf) (set! factor nf)]
    [method price! (np) (set! price np)]
    [method make ()
            (OBJECT ([field price price])
                    ([method val () (* price factor)]))])))

> (define o1 (-> factory make))
> (-> o1 val)
10
> (-> factory factor! 2)
> (-> o1 val)
20
> (-> factory price! 20)
> (-> o1 val)
20
> (define o2 (-> factory make))
> (-> o2 val)
40

在Java中你能这么做吗?

请验证这些返回。

results matching ""

    No results matching ""