Ping 16/Oct/2020

Posted on Oct 16, 2020

ADT1만큼 만들어 쓰는 것은 아니지만, 그냥 Kotlin, Python의 dataclass 정도는 쓰고 싶어서 CommonLisp 매크로를 만들어봤다.2

equals 라이브러리를 써서 동치 비교와 CLOS의 print-object generic function을 구현해서 to-string 정도만 되도록 만들어 봤다.

macro body

(defmacro define-data-class
    (class-name parent-classes slot-names
     &key (no-print-slot-names nil) (no-equals-slot-names nil))
  (flet ((accessor-symbol (slot-name)
           (alexandria:format-symbol *package*
                                     "~a-~a" class-name slot-name)))  
    (list 'progn          
          ;; defclass
          (let ((slot-defs (loop for slot-name in slot-names
                                 collecting `(,slot-name
                                              :initarg ,(alexandria:make-keyword slot-name)
                                              :accessor ,(accessor-symbol slot-name)))))
            `(defclass ,class-name ,parent-classes ,slot-defs))

          ;; defmethod print-object
          (let* ((print-slot-names (set-difference slot-names
                                                   no-print-slot-names))
                 (fmt-ctrl (format nil "~{~a=~~a~^, ~}" print-slot-names)))
            `(defmethod print-object ((obj ,class-name) stream)
               (print-unreadable-object (obj stream :type t)
                 (with-slots ,print-slot-names obj
                   (format stream ,fmt-ctrl ,@print-slot-names)))))

          ;; defmethod equals:equals
          (let* ((equals-slot-names (set-difference slot-names
                                                    no-equals-slot-names))
                 (equals-exprs (loop for equals-slot-name in equals-slot-names
                                     collecting `(equals:equals (slot-value lhs (quote ,equals-slot-name))
                                                                (slot-value rhs (quote ,equals-slot-name))))))
            `(defmethod equals:equals ((lhs ,class-name) (rhs ,class-name) &rest args)
               (declare (ignore args))
               (and t ,@equals-exprs)))
          )))

synopsis / usage

(define-data-class animal () (name age))

(define-data-class bird (animal) ())

(equals:equals (make-instance 'animal :name "foo" :age 18)
               (make-instance 'animal :name "foo" :age 18))

오랬만에 재밌었다.

Footnotes


2

사실 ADT을 정의하는 매크로 라이브러리와 이를 위한 패턴매칭 라이브러리가 커먼리습에 이미 있지만. 더 간단하게만 쓰고 싶어서.