動機

之前欠的要補…

希望你知道…

  1. interpreter的結構
  2. scheme(或lisp)

class的演化

lambda的部分是interpreter的一部份code

其實這裡的重點是struct(這裡叫record)有什麼,只要了解為什麼struct這樣設計大概就會了解到底發生什麼事

class & instance

把東西分成class與class之下的instance

instance有

  • 其所屬的class
  • attribute的值

class有

  • class var的symbol(名字)
  • class var的值
  • attribute的symbol(名字)
  • method的表(method_name => real_proc)
(define-record instance (class i-vals))
(define-record class (c-vars c-vals i-vars m-env))

(lambda (exp env class inst)
    (variant-case exp
                  (i-varref (var)
                            (lookup var
                                    (class->i-vars (something->value class))
                                    (instance->i-vals (something->value inst))))
                  (c-varref (var)
                            (lookup var
                                    (class->c-vars (something->value class))
                                    (class->c-vals (something->value class))))
                  (i-varassign (var exp)
                               (let ((value (eval-exp exp env class inst)))
                                 (void->expressed
                                  (assign! var
                                           value
                                           (class->i-vars (something->value class))
                                           (instance->i-vals (something->value inst))))))
                  (c-varassign (var exp)
                               (let ((value (eval-exp exp env class inst))
                                     (c-vals (class->c-vals (something->value class))))
                                 (void->expressed
                                  (assign! var
                                           value
                                           (class->c-vars (something->value class))
                                           c-vals))))
                  (method (formals body)
                          (let ((new-formals (cons 'self formals)))
                            (open-method->expressed
                             (lambda (class-thunk) ;; class has not instanced, wait for the class here
                               (lambda (args)
                                 (eval-exp
                                  body
                                  (extend-env new-formals (map expressed->denoted args) env)
                                  (make-something (class-thunk))
                                  (make-something (car args))))))))
                  (meth-app (name rands)
                            (let ((args (map (lambda (rand)
                                               (eval-exp rand env class inst))
                                             rands)))
                              (meth-call name (instance->class (car args)) args)))
                  (new-simpleinst (class-exp)
                                  (let ((inst-class (eval-exp class-exp env class inst)))
                                    (let ((new-inst (make-instance inst-class
                                                                   (make-vals (class->i-vars inst-class)))))
                                      (ignore (meth-call 'initialize inst-class (list new-inst)))
                                      new-inst)))
                  (new-simpleclass (c-vars i-vars methdecls init-exp)
                                   (let ((open-methods
                                          (map (lambda (decl)
                                                 (expressed->open-method
                                                  (eval-exp (decl->exp decl) env class inst)))
                                               methdecls)))
                                     (letrec ((new-class
                                               (make-class
                                                c-vars
                                                (make-vals c-vars)
                                                i-vars
                                                (extend-method-env
                                                 (map decl->var methdecls)
                                                 (map (lambda (open-meth)
                                                        (open-meth (lambda () new-class)))
                                                      open-methods)
                                                 init-meth-env))))
                                       (ignore (eval-exp init-exp
                                                         env
                                                         (make-something new-class)
                                                         (make-nothing)))
                                       new-class)))))

inherence

現在class可以往上找,所以多一個parent指到另一個class

(define-record instance (class i-vals))
(define-record class (parent c-vars c-vals i-vars m-env))

(lambda (exp env class inst)
    (variant-case exp
                  (new-class (parent-exp c-vars i-vars methdecls init-exp)
                             (let ((parent-class (eval-exp parent-exp env class inst))
                                   (open-methods
                                    (map (lambda (decl)
                                           (expressed->open-method
                                            (eval-exp (decl->exp decl) env class inst)))
                                         methdecls)))
                               (let ((new-c-vars (append c-vars (class->c-vars parent-class)))
                                     (new-i-vars (append i-vars (class->i-vars parent-class)))
                                     (new-c-vals ; NEW!
                                      (make-shared-c-vals (class->c-vals parent-class) c-vars)))
                                 (letrec ((new-class
                                           (make-class
                                            (make-something parent-class)
                                            new-c-vars
                                            new-c-vals
                                            new-i-vars
                                            (extend-method-env
                                             (map decl->var methdecls)
                                             (map (lambda (open-method)
                                                    (open-method (lambda () new-class)))
                                                  open-methods)
                                             (class->m-env parent-class)))))
                                   (ignore
                                    (eval-exp init-exp
                                              env
                                              (make-something new-class)
                                              (make-nothing)))
                                   new-class))))
                  (super-meth-app (name rands)
                                  (let ((args (map (lambda (x) (eval-exp x env class inst)) rands)))
                                    (meth-call name
                                               (something->value
                                                (class->parent
                                                 (something->value class)))
                                               (cons (something->value inst) args))))
                  ;; Figure 7.1.3 : page 219
                  (i-varref (var)
                            (lookup var
                                    (class->i-vars (something->value class))
                                    (instance->i-vals (something->value inst))))
                  (c-varref (var)
                            (lookup var
                                    (class->c-vars (something->value class))
                                    (class->c-vals (something->value class))))
                  (i-varassign (var exp)
                               (let ((value (eval-exp exp env class inst)))
                                 (void->expressed
                                  (assign! var
                                           value
                                           (class->i-vars (something->value class))
                                           (instance->i-vals (something->value inst))))))
                  (c-varassign (var exp)
                               (let ((value (eval-exp exp env class inst))
                                     (c-vals (class->c-vals (something->value class))))
                                 (void->expressed
                                  (assign! var
                                           value
                                           (class->c-vars (something->value class))
                                           c-vals))))
                  (method (formals body)
                          (let ((new-formals (cons 'self formals)))
                            (open-method->expressed
                             (lambda (class-thunk)
                               (lambda (args)
                                 (eval-exp
                                  body
                                  (extend-env new-formals (map expressed->denoted args) env)
                                  (make-something (class-thunk))
                                  (make-something (car args))))))))
                  (meth-app (name rands)
                            (let ((args (map (lambda (rand)
                                               (eval-exp rand env class inst))
                                             rands)))
                              (meth-call name (instance->class (car args)) args)))
                  (new-simpleinst (class-exp)
                                  (let ((inst-class (eval-exp class-exp env class inst)))
                                    (let ((new-inst (make-instance inst-class
                                                                   (make-vals (class->i-vars inst-class)))))
                                      (ignore (meth-call 'initialize inst-class (list new-inst)))
                                      new-inst)))
                  ;; The following is a convenience to make old examples work (stackclass).
                  ;; Syntax-expand can't handle simpleclass, because then it would be
                  ;; expanded away in section 7.1
                  (new-simpleclass (c-vars i-vars methdecls init-exp)
                                   (eval-exp (make-new-class (make-varref 'baseobject)
                                                             c-vars i-vars methdecls init-exp)
                                             env class inst))))

metaclass

從繼承那邊可以看出來,其實parent與class(struct中的ptr)很像,都是往被指到的東西中找值,所以其實我們也不用區分是class還是instance

把class與instance和在一起,會發現,我們不需要c-vals與c-vars,因為

  • 如果要instance變數,就看i-vals
  • 如果要class變數,就看class的i-vals

到這裡就可以知道,為什麼python與ruby的class都可以有自己的變數,同時可以在某種程度上當class變數用

(define-record instance (class parent i-vars m-env i-vals))

(lambda (exp env class inst)
  (variant-case exp
                (c-varref (var) 
                          (lookup var
                                  (class->i-vars
                                   (something->value
                                    (instance->class
                                     (something->value class))))
                                  (instance->i-vals
                                   (something->value class))))
                (c-varassign (var exp)
                             (let ((value (eval-exp exp env class inst)))
                               (void->expressed
                                (assign! var
                                         value
                                         (class->i-vars
                                          (something->value
                                           (instance->class
                                            (something->value class))))
                                         (instance->i-vals
                                          (something->value class))))))
                (new-instance (class-exp parent-exp i-vars methdecls)
                              (let ((inst-class (eval-exp class-exp env class inst))
                                    (parent-class (eval-exp parent-exp env class inst))
                                    (open-methods
                                     (map (lambda (decl)
                                            (expressed->open-method
                                             (eval-exp (decl->exp decl) env class inst)))
                                          methdecls)))
                                (let ((new-i-vars (append i-vars (class->i-vars parent-class))))
                                  (letrec ((new-inst
                                            (make-instance
                                             (make-something inst-class)
                                             (make-something parent-class)
                                             new-i-vars
                                             (extend-method-env (map decl->var methdecls)
                                                                (map (lambda (open-method)
                                                                       (open-method (lambda () new-inst)))
                                                                     open-methods)
                                                                (class->m-env parent-class))
                                             (make-vals (class->i-vars inst-class)))))
                                    (ignore (meth-call 'initialize inst-class (list new-inst)))
                                    new-inst))))
                ;; Figure 7.2.4 : page 228
                (super-meth-app (name rands)
                                (let ((args (map (lambda (x) (eval-exp x env class inst)) rands)))
                                  (meth-call name
                                             (something->value
                                              (class->parent
                                               (something->value class)))
                                             (cons (something->value inst) args))))
                ;; Figure 7.1.3 : page 219
                (i-varref (var)
                          (lookup var
                                  (class->i-vars (something->value class))
                                  (instance->i-vals (something->value inst))))
                (i-varassign (var exp)
                             (let ((value (eval-exp exp env class inst)))
                               (void->expressed
                                (assign! var
                                         value
                                         (class->i-vars (something->value class))
                                         (instance->i-vals (something->value inst))))))
                (method (formals body)
                        (let ((new-formals (cons 'self formals)))
                          (open-method->expressed
                           (lambda (class-thunk)
                             (lambda (args)
                               (eval-exp
                                body
                                (extend-env new-formals (map expressed->denoted args) env)
                                (make-something (class-thunk))
                                (make-something (car args))))))))
                (meth-app (name rands)
                          (let ((args (map (lambda (rand)
                                             (eval-exp rand env class inst))
                                           rands)))
                            (meth-call name
                                       (something->value (instance->class (car args)))
                                       args)))))

python

從剛剛metaclass的struct來看,先不管i-vals與i-vars與m-env

看到下面的code的class C 可以發現 B 是 parent,而A是 class

再看到type.__new__(ins, name,parent,attrs) attrs就是Dict,可以放值或是function,可以當成i-vals與i-vars與m-env的結合

所以ins是 class, parent是parent,這裡生的就是instance

剛剛是把python的metaclass與之前說的metaclass做結合, 剩下的部分就是__new__與__init__,__new__就是會丟出instance,而__init__就是instance的終點 另外super就是找繼承鍊上的instance,詳情可以看Ref的文章

class A(type):
    def __new__(ins, name, parent, attrs):
        print("in A")
        attrs['ins_var_of_C'] = 101
        ins.class_var_of_C = '123'
        ret = type.__new__(ins, name,parent,attrs)
        return ret
    def from_A(self):
        return 1000

class B(object):
    def __new__(ins):
        print("in B")
        ins.b = 19
        return super().__new__(ins)

class C(B,metaclass=A):
    def __new__(ins):
        print("in C")
        ins.from_here = 876
        return super().__new__(ins)
    def __init__(self):
        self.a = 999
C.from_A()
c = C()
print(c.b)
print(c.a)
print(c.from_here)
print(C.ins_var_of_C)
print(C.class_var_of_C)
print(c.ins_var_of_C)

ruby

與python不同的是,class的部分(metaclass in Python),沒有指定的機會,Ruby會自己生

但是Ruby有eval來做許多壞事

instance_eval 就是用 之前提到的instance來做eval,可以想像成用instance的所有資料作為跑code的環境 class_eval 其實就是會檢查 執行的obj是不是class,其實用{class}.instance_eval是等於{class}.class_eval的

class Object
   # The hidden singleton lurks behind everyone
   def metaclass; class << self; self; end; end
   def meta_eval &blk; metaclass.instance_eval &blk; end

   # Adds methods to a metaclass
   def meta_def name, &blk
     meta_eval { define_method name, &blk }
   end

   # Defines an instance method within a class
   def class_def name, &blk
     class_eval { define_method name, &blk }
   end
 end

 class MailTruck
   def self.company( name )
     meta_def :company do; name; end
     # 吃當下的class的metaclass來加method,也就是替被執行該method的class加一個class method,叫company
   end
 end

  class HappyTruck < MailTruck
   company "Happy's -- We Bring the Mail, and That's It!"
 end

  class Creature
   def self.traits( *arr )
     return @traits if arr.empty?
     attr_accessor *arr
     arr.each do |trait|
       meta_def trait do |val|
         @traits ||= {}
         @traits[trait] = val
       end
     end
     class_def :initialize do
       self.class.traits.each do |k,v|
         instance_variable_set( "@#{k}", v )
       end
     end
     class_def :hi do
      self.class.traits.each do |k,_|
        puts "#{self.instance_eval "self.#{k}"}"
      end
    end
   end
 end

 class A < Creature
  traits :a, :b, :c
 end
 A.a 10
 A.b 20
 A.c 30
 x = A.new
 y = A.new
 puts "x content"
x.hi
puts "y content"
y.hi
puts "change"
y.b = 100
y.hi

Ref

EoPL1 Ch10 Metaprogramming in Ruby 你不知道的 super