Re: Metaprogramming et al
От: CrazyPit  
Дата: 11.07.05 12:58
Оценка: 36 (5)
Да интересная дискуссия, даже зарегился на RSDN, чтоб сюда запостить.
С Лиспом я знаком года 2-3, с тех пор как плотно начал юзать емакс, но именно CL, ну и схемой, заинтересовался только в конце прошлого года. Сначала прочитал, правда не до конца, первый том "Мир Лиспа", как то мне не очень понравилось и я забил, так что не советую вам её читать, очень скучно плюс только в том, что про Common Lisp это единственная книга на русском. Потом, когда появилась в инете Practical Common Lisp, и я его прочитал, да ещё открыл для себя SLIME, всё стало намного интереснее. Язык ИМХО самый навороченный из всех, и причина этому именно макросы в сочетании с простым представлением программ и данных. Чего стоят те же макросы loop & format.

Вы тут обсуждали отсутствие стат. типизации, я вспомнил про declare, хотя это не совсем то, но во первых она выдаёт варнинги при компляции, если при вызове указан не тот тип + ускоряет выполнение, не то что вам нужно? Только конечно это не распространяется на стандартные функции. Я написал небольшой макрос, который делает возможным быстрое объявлении функций, с заданным типом аргументов.

(defmacro defun-declare-type (name (&rest params) &body body)
  `(defun ,name ,(mapcar #'cadr params)
    (declare ,@params)
    ,@body))

теперь заместо:
(defun test (b a)
  (declare (string b) (integer a))
  (format t "~a have ~d apples." b a))


я могу писать так:
(defun-declare-type test ((string b) (integer a))
  (format t "~a have ~d apples." b a))


И при создании функции, с не правильным типом второго аргумента в вызове test:
(defun-declare-type test2 ((string a) )
  (test "ABC" a))


Выдаётся варнинг:
In: DEFUN-DECLARE-TYPE TEST2

;   (TEST "AA" A)
; ==>
;   A
; Warning: Result is a BASE-STRING, not a (VALUES &OPTIONAL INTEGER &REST T).
; 

; Compilation unit finished.
;   1 warning



совсем маленькое улучшение, но оно показывает, как легко в язык добавляются новые конструкции, причём по нажатию М-. или во время отладки я всёравно попадаю на определения этой функции.
Можно привести пример посложнее из PCL, макрос генерирует класс бинарных данных:

(define-binary-class id3v2.3-tag (id3-tag)
  ((extended-header-size (optional :type 'u4 :if (extended-p flags)))
   (extra-flags          (optional :type 'u2 :if (extended-p flags)))
   (padding-size         (optional :type 'u4 :if (extended-p flags)))
   (crc                  (optional :type 'u4 :if (crc-p flags extra-flags)))
   (frames               (id3-frames :tag-size size :frame-type 'id3v2.3-frame))))



превращается в:

(define-generic-binary-class id3v2.3-tag
    (id3-tag)
  ((extended-header-size 
    (optional :type 'u4 :if (extended-p flags)))
   (extra-flags 
    (optional :type 'u2 :if (extended-p flags)))
   (padding-size 
    (optional :type 'u4 :if (extended-p flags)))
   (crc 
    (optional :type 'u4 :if (crc-p flags extra-flags)))
   (frames 
    (id3-frames :tag-size size :frame-type 'id3v2.3-frame)))
  (defmethod com.gigamonkeys.binary-data::read-object progn
    ((#:objectvar id3v2.3-tag) #:streamvar) 
    (declare (ignorable #:streamvar))
    (with-slots (identifier major-version revision flags size extended-header-size
                extra-flags padding-size crc frames)
    #:objectvar
      (setf extended-header-size
        (read-value 'optional #:streamvar :type 'u4 :if (extended-p flags)))
      (setf extra-flags
        (read-value 'optional #:streamvar :type 'u2 :if (extended-p flags)))
      (setf padding-size
        (read-value 'optional #:streamvar :type 'u4 :if (extended-p flags)))
      (setf crc
        (read-value 'optional #:streamvar :type 'u4 :if (crc-p flags extra-flags)))
      (setf frames
        (read-value 'id3-frames #:streamvar :tag-size size :frame-type 'id3v2.3-frame)))))



А вот сам код макроса:
(defmacro define-generic-binary-class (name (&rest superclasses) slots read-method)
  (with-gensyms (objectvar streamvar)
    `(progn
       (eval-when (:compile-toplevel :load-toplevel :execute)
         (setf (get ',name 'slots) ',(mapcar #'first slots))
         (setf (get ',name 'superclasses) ',superclasses))

       (defclass ,name ,superclasses
         ,(mapcar #'slot->defclass-slot slots))

       ,read-method

       (defmethod write-object progn ((,objectvar ,name) ,streamvar)
         (declare (ignorable ,streamvar))
         (with-slots ,(new-class-all-slots slots superclasses) ,objectvar
           ,@(mapcar #'(lambda (x) (slot->write-value x streamvar)) slots))))))

(defmacro define-binary-class (name (&rest superclasses) slots)
  (with-gensyms (objectvar streamvar)
    `(define-generic-binary-class ,name ,superclasses ,slots
       (defmethod read-object progn ((,objectvar ,name) ,streamvar)
         (declare (ignorable ,streamvar))
         (with-slots ,(new-class-all-slots slots superclasses) ,objectvar
           ,@(mapcar #'(lambda (x) (slot->read-value x streamvar)) slots))))))

(defun slot->defclass-slot (spec)
  (let ((name (first spec)))
    `(,name :initarg ,(as-keyword name) :accessor ,name)))

(defun new-class-all-slots (slots superclasses)
  (nconc (mapcan #'all-slots superclasses) (mapcar #'first slots)))

(defun slot->write-value (spec stream)
  (destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
    `(write-value ',type ,stream ,name ,@args)))

(defun slot->read-value (spec stream)
  (destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
    `(setf ,name (read-value ',type ,stream ,@args))))


Уже сложнее, но зато получается очень хорошая абстракция, не говоря уже об уменьшении объёма кода.
И именно благодаря столь-удачному подходу к макропрограммированию CL можно ИМХО назвать самым мощным ЯП общего применения.

Ещё мне очень нравиться скорость выполнения, с учётом дин. типизации.
Я как-то переписывал одну вычислительную программку с Python — на CL, так вот при использовании компиляторов CMUCL или SBCL скорость выполнения возросла в 4-5 раз, при примерно одинаковом количестве кода. В CL можно было бы сделать оптимизацию, и тогда бы скорость наверное возросла ещё в несколько раз, но мне было лень.

Ну и конечно REPL + инкрементальная компиляция очень сильно облегчает (можно сказать делает более увлекательным что-ли ) написание и тестирование программ.

Тем кому не нравятся скобки могу ответить, что нужно юзать структурное кодирование, парные скобки вставляются автоматически, и с помощью С-М- комбинаций можно легко передвигаться по сколь-угодно сложным спискам. То есть скобки здесь это огромны плюс.
Вот напримерпример есть функция:

(defun foo (bar baz)
  (+ (* baz 2)
     (expt bar (* baz 2)^)))


Курсор стоит на место каре, хочу я добавить let к этому выражению,
нажимаю два раза С-M-U(ctrl-alt-u), курсор переноситься на два уровне вверх, т.е. перед скобкой, стоящей перед плюсом. потом нажимаю M-1 ( скобки автоматом добавляются как в начале так и в конце:

(defun foo (bar baz)
  ((+ (* baz 2)
     (expt bar (* baz 2)))))


При таком подходе намного реже требуется пользоваться стрелками, потомучто мы работаем не с последовательностью знаков, а с вложенными списками и лисп-символами, т.е. над более высокими абстракциями. к тому же префикс C-M- для удобства можно заменить просто на C-.

Можно говорить очень много об отдельных мегафичах CL, таких как например CLOS, но это отдельные и очень объёмные темы.
Так что всё-таки всем советую прочитать хотя-бы Practical Common Lisp, чтобы понять все преимущества Лиспа.

Ну и напоследок хочу сказать что мне не нравиться. Так как стандарт создавался почти 20 лет назад, а он основывался на намного более ранних разработках, в нём присутствует достаточное количество небольших несогласованностей, как например порядок аргументов в функциях доступа к n-ому элементу массива и списка, или невнятные названия некоторых стандартных функций, или атавизмов, как ключ &aux в определении функций. Но в принципе это не играет особой роли при разработке.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.