如何验证 class 属性的初始值?

假设有以下定义:

#+BEGIN_SRC emacs-lisp :results output :async
(defclass person ()
  ((name :initarg :name)
   (age :initarg :age
        :initform 0)))

(defmethod person-info ((p person))
  (princ (format "name: %s, age: %s\n"
                 (oref p name)
                 (oref p age))))

(setq joe (person :name "Joe"
                  :age -1))

(person-info joe)
#+END_SRC

#+RESULTS:
: name: Joe, age: -1

如何避免 age 设置不合理的初始值?

(cl-deftype age () '(satisfies (lambda (x) (>= x 0))))
;; This works better in particular case.
(cl-deftype age () '(integer 0))

(defclass foo ()
  ((x :type age :initarg :x)))

;; Or straightforwardly
(defclass foo ()
  ((x :type (integer 0) :initarg :x)))

;; `make-instance' is more idiomatic in CL, but `foo' would also work (Emacs Lisp only)
(make-instance 'foo :x 0)
;; => #s#s(eieio-class ...

(make-instance 'foo :x -1)
;; Lisp error: (invalid-slot-type foo x age -1)

;; Or just
(cl-check-type -1 age)
;; Lisp error: (wrong-type-argument age -1 -1)

http://www.lispworks.com/documentation/lw50/CLHS/Body/m_deftp.htm#deftype

http://www.lispworks.com/documentation/lw50/CLHS/Body/m_defcla.htm#defclass

值得注意的是不是所有 CLOS 实現都会把 :type 當回事兒,不過至少 eieio 是会做类型檢查的,尽管不是完整的 CLOS。

3 个赞

听起来各方似乎意见不太统一,就怕 eieio 哪天也认为 :type 是个鸡肋。

感觉 defstruct 更像是 class,有 :constructor,也能“继承”。

好混乱。


APPENDED 2018-11-01 18.16.44

访问 struct 的成员可以通过 (<struct-name>-<member-name> <instance>),而 class 有 :accessor 可以任意更改这个函数名,可能有时候会比较便利,但我觉得还是 struct 省事:

#+BEGIN_SRC emacs-lisp :results value :async
(require 'cl)

(defstruct bar
 c)

(bar-c (make-bar :c 3))
#+END_SRC

#+RESULTS:
: 3

#+BEGIN_SRC emacs-lisp :results value :async
(require 'eieio)

(defclass qux ()
  ((d :initarg :d
      :accessor get-d)))

(get-d (make-instance 'qux :d 4))
#+END_SRC

#+RESULTS:
: 4
1 个赞

defstruct 不能多重继承。自然也沒有 method combination。


:accessor 的作用在于它定义的是个 generic function:

(defclass a () ((x :accessor age :initform 1)))
(defclass b () ((y :accessor age :initform 'z)))

(age (make-instance 'a))
; => 1
(age (make-instance 'b))
; => z

沒有一定項目規模自然难以体会用 OOP 的好处。

2 个赞

有没有相关阅读,求分享。flavor那个我看过了

Flavor 反而没啥实用意义,主要也就历史指导意义,毕竟已经被废弃了。

大部分设计都能在 CLtL2 找到,关于 CLOS 的还有 The Art of Metaobject Protocol

实在感兴趣的话 Lisp Machine Manual 提到了 defstruct 的继承功能和 Flavor 的对比。

flavor挺有意思的。倒不是说为了实用才看。感谢。smalltalk的设计和CLOS区别大吗?(我不了解)有时间我想学malltalk感受一下,有什么建议吗?

CLOS 是参照 Smalltalk (以及 Flavor 和 Common LOOPS)设计的,本质区别不大,主要也就 Generic function 和 Message passing 的区别。Smalltalk 的书只知道有个 blue book。说实话个人觉得 Maude 更有趣些,它用的是基于 包结构的 OOP,和 Ocaml 以及 Ada 类似。

1 个赞