← Архив: Common Lisp

Форум "Common Lisp"

Author: · 01.03.2009 10:56
· original author: archimag
Данные форум предназначен для обсуждения вопросов, связанных с программированием на Common Lisp.
Добро пожаловать!
· original author: treep
Итак, для начала, пара макросов с побочным эффектом :)
Сделал их главым образом из-за привычки к типичным языкам, где разные, но подобные, опереции для разных типов осуществляет один оператор.
;; один аргумент изменяется в зависимости от своего значения/типа и значения/типа другого аргумента.

; Вспомогательные функционалы
(defmacro =fun (fun var &rest args) `(setf ,var (,fun ,var ,@args)))
(defmacro =fun-spec (fun spec var &rest args) `(setf ,var (,fun ,spec ,var ,@args)))
; Перегруженый инкрементор
(defmacro =+ (var &rest args)
    `(cond
           ( (typep ,var 'list)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'list) (=fun-spec concatenate 'list ,var arg) )
                            ( (typep arg 'number) (=fun-spec concatenate 'list ,var (list arg)) )
                            ( (typep arg 'string) (=fun-spec concatenate 'list ,var (list arg)) )
                            ( t (format t "Несовместимый тип аргумента ~a" arg) )
)
)

                    ,var
)
)

           ( (typep ,var 'string)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'string) (=fun-spec concatenate 'string ,var arg) )
                            ( (typep arg 'number) (=fun-spec concatenate 'string ,var (write-to-string arg)) )
                            ( t (format t "Несовместимый тип аргумента ~a~%" arg) )
)
)

                    ,var
)
)

           ( (typep ,var 'number)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'number) (=fun + ,var arg) )
                            ( (typep arg 'string) (=fun + ,var (read-from-string arg)) )
                            ( t (format t "Несовместимый тип аргумента ~a~%" arg) )
)
)

                    ,var
)
)

           ( t (format t "Аргумент неизвестного типа ~a~%" ,var) )
)
)

Макрос =+ действует понятно как, но он позволяет перемешивать разные типы, например:
(setf i 0)
  ;-> 0
(=+ i 1 2 3 "4" "5" "0.001")
  ;-> 15.001

(setf lst '())
  ;-> NIL
(=+ lst 1 2 '(3 4) '((5 6)) "7" "8")
  ;-> (1 2 3 4 (5 6) "7" "8")

(setf str "str:")
  ;-> "str:"
(=+ str " now is " 1 2 3 0.001)
  ;-> "str: now is 1230.001"
Анологично определяем ещё
; Перегруженый мультипликатор
(defmacro =* (var &rest args)
    `(cond
           ( (typep ,var 'list)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'integer) (let ((tmp ,var)) (dotimes (i (1- arg)) (=fun-spec concatenate 'list ,var tmp))))
                            ( t (format t "Несовместимый тип аргумента ~a" arg) )
)
)

                    ,var
)
)

           ( (typep ,var 'string)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'integer) (let ((tmp ,var)) (dotimes (i (1- arg)) (=fun-spec concatenate 'string ,var tmp))))
                            ( t (format t "Несовместимый тип аргумента ~a~%" arg) )
)
)

                    ,var
)
)

           ( (typep ,var 'number)
             (progn (dolist (arg (list ,@args))
                      (cond ( (typep arg 'number) (=fun * ,var arg) )
                            ( (typep arg 'string) (=fun * ,var (read-from-string arg)) )
                            ( t (format t "Несовместимый тип аргумента ~a~%" arg) )
)
)

                    ,var
)
)

           ( t (format t "Аргумент неизвестного типа ~a~%" ,var) )
)
)

И в итоге:
(setf i 1)
  ;-> 1
(=* i 2 3 4 "5")
  ;-> 120
(=* i "1.001")
  ;-> 120.12

(setf lst '(a b c))
  ;-> (A B C)
(=* lst 3)
  ;-> (A B C A B C A B C)

(setf str "Foo")
  ;-> "Foo"
(=* str 2 3)
  ;-> "FooFooFooFooFooFoo"
· original author: treep
На основе их можно определить макросы уже без побочных эффектов:
;; Перегруженный +, работает для чисел, строк и списков. Не имеет побочного эффекта.
(defmacro +. (frst &rest rst)
    `(let ((result ,frst)) (=+ result ,@rst))
)

;; Перегруженный *.
(defmacro *. (frst &rest rst)
    `(let ((result ,frst)) (=* result ,@rst))
)

(+. 1 2 "3" "0.001")
  ;-> 6.001
(+. "str " 2 3 "0.001")
  ;-> "str 230.001"
(+. '(1 2 3) 4 5 "0.001")
  ;-> (1 2 3 4 5 "0.001")

О точке - она не просто, чтобы не путаться с обычными + и *, а значит примерно вот что - в ruby или python любая переменная/константа это объект определенного типа (точнее экземпляры), и любые функции для них мыслятся как методы. Например - 7 + 8 + 9 на самом деле вычисляется как 7.+(8.+(9)). Объект слева, метод справа и между ними точка. В Лисп наоборот объект (первый аргумент) справа, а метод (функция) - слева, и между ними я ставлю точку.
Теперь если ещё добавить
(defun range (a b)
  (let ((result nil))
    (dotimes (i (1+ (- b a)))
      (push (+ a i) result)
)

    (nreverse result)
)
)

(range 1 5)
  ;-> (1 2 3 4 5)

то можно определить факториал, например так:
(defmacro fact (n)
    `(progn
        (setf *MAX-RECURSION* (* *O-RECURSIVE-COEFF* (exp n)))
        (*. ,@(range 1 n))
)
)

(fact 1000000)
  ;-> И через несколько минут...