如何在Common Lisp中编写类似的函数?

nis*_*gao 8 preprocessor dry common-lisp

我正在从Practical Common Lisp学习Common Lisp.它有一个辅助函数的例子,用于在第24章中读取和写入二进制文件.这是一个例子:

(defun read-u2 (in)
  (+ (* (read-byte in) 256) (read-byte in)))
Run Code Online (Sandbox Code Playgroud)

我也可以编写用于读取其他类型二进制数的函数.但我认为这样做违反了DRY原则.此外,这些函数将是类似的,所以我尝试用宏生成函数.

(defmacro make-read (n be)
  `(defun ,(intern (format nil "READ~d~:[L~;B~]E" n be))
       (&optional (stream *standard-input*))
     (logior ,@(loop for i from 0 below n collect
                `(ash (read-byte stream)
                      ,(* 8 (if be (- n 1 i) i)))))))

(defmacro make-read-s (n be)
  `(defun ,(intern (format nil "READ~d~:[L~;B~]E-S" n be))
       (&optional (stream *standard-input*))
     (let ((a (,(intern (format nil "READ~d~:[L~;B~]E" n be)) stream)))
       (if (zerop (logand a ,(ash 1 (1- (* 8 n)))))
       a
       (logior a ,(ash -1 (* 8 n)))))))

(defmacro make-write (n be)
  `(defun ,(intern (format nil "WRITE~d~:[L~;B~]E" n be))
       (n &optional (stream *standard-output*))
     (setf n (logand n ,(1- (ash 1 (* 8 n)))))
     ,@(loop for i from 0 below n collect
        `(write-byte (ldb (byte 8 ,(* 8 (if be (- n 1 i) i))) n)
                     stream))))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (dolist (cat '("READ" "READ-S" "WRITE"))
    (dolist (be '(nil t))
      (dolist (n '(1 2 4 8))
        (eval `(,(intern (format nil "MAKE-~a" cat)) ,n ,be))))))
Run Code Online (Sandbox Code Playgroud)

有用.它生成用于读取和写入大小为1,2,4和8的无符号和有符号整数的函数.SLIME理解它.但我想知道是否有更好的方法.

在Common Lisp中编写一堆类似函数的最佳方法是什么?

Rai*_*wig 9

这段代码存在一些问题,尽管让宏生成函数的一般方法很好.

命名

不应该命名宏make-...,因为它们不是创建某些东西的函数,而是定义函数的宏.

代码生成

EVAL-WHEN ... EVAL代码是真的不好,不应该使用这种方式.

更好的方法是progn使用函数定义编写扩展为a的宏.

如果我想使用EVAL,那么我不需要编写代码生成宏,而只需编写代码生成函数.但是我不想使用EVAL,我想直接为编译器创建代码.如果我有代码生成宏,那么我不需要EVAL.

EVAL这不是一个好主意,因为不清楚代码是否会被编译 - 这将取决于实现.评估也将在编译时和加载时进行.最好在编译时编译函数,只在加载时加载它们.文件编译器也可能错过对已评估函数的可能优化.

(defmacro def-read-fun (n be)
  `(defun ,(intern (format nil "READ~d~:[L~;B~]E" n be))
          (&optional (stream *standard-input*))
     (logior ,@(loop for i from 0 below n collect
                     `(ash (read-byte stream)
                           ,(* 8 (if be (- n 1 i) i)))))))

(defmacro def-read-s-fun (n be)
  `(defun ,(intern (format nil "READ~d~:[L~;B~]E-S" n be))
          (&optional (stream *standard-input*))
     (let ((a (,(intern (format nil "READ~d~:[L~;B~]E" n be)) stream)))
       (if (zerop (logand a ,(ash 1 (1- (* 8 n)))))
           a
         (logior a ,(ash -1 (* 8 n)) )))))

(defmacro def-write-fun (n be)
  `(defun ,(intern (format nil "WRITE~d~:[L~;B~]E" n be))
          (n &optional (stream *standard-output*))
     (setf n (logand n ,(1- (ash 1 (* 8 n)))))
     ,@(loop for i from 0 below n collect
             `(write-byte (ldb (byte 8 ,(* 8 (if be (- n 1 i) i))) n)
                          stream))))
Run Code Online (Sandbox Code Playgroud)

而不是EVAL-WHEN ... EVAL我们定义另一个宏,然后我们稍后使用它:

(defmacro def-reader/writer-functions (cat-list be-list n-list)
  `(progn
     ,@(loop for cat in cat-list append
             (loop for be in be-list append
                   (loop for n in n-list
                         collect `(,(intern (format nil "DEF-~a-FUN" cat))
                                   ,n
                                   ,be))))))
Run Code Online (Sandbox Code Playgroud)

现在我们可以使用上面的宏来生成所有函数:

(def-reader/writer-functions
 ("READ" "READ-S" "WRITE")
 (nil t)
 (1 2 4 8))
Run Code Online (Sandbox Code Playgroud)

你可以在这里看到扩展:

CL-USER 173 > (pprint (macroexpand-1 '(def-reader/writer-functions
                                       ("READ" "READ-S" "WRITE")
                                       (nil t)
                                       (1 2 4 8))))

(PROGN
  (DEF-READ-FUN 1 NIL)
  (DEF-READ-FUN 2 NIL)
  (DEF-READ-FUN 4 NIL)
  (DEF-READ-FUN 8 NIL)
  (DEF-READ-FUN 1 T)
  (DEF-READ-FUN 2 T)
  (DEF-READ-FUN 4 T)
  (DEF-READ-FUN 8 T)
  (DEF-READ-S-FUN 1 NIL)
  (DEF-READ-S-FUN 2 NIL)
  (DEF-READ-S-FUN 4 NIL)
  (DEF-READ-S-FUN 8 NIL)
  (DEF-READ-S-FUN 1 T)
  (DEF-READ-S-FUN 2 T)
  (DEF-READ-S-FUN 4 T)
  (DEF-READ-S-FUN 8 T)
  (DEF-WRITE-FUN 1 NIL)
  (DEF-WRITE-FUN 2 NIL)
  (DEF-WRITE-FUN 4 NIL)
  (DEF-WRITE-FUN 8 NIL)
  (DEF-WRITE-FUN 1 T)
  (DEF-WRITE-FUN 2 T)
  (DEF-WRITE-FUN 4 T)
  (DEF-WRITE-FUN 8 T))
Run Code Online (Sandbox Code Playgroud)

然后将每个子表单扩展为函数定义.

这样编译器就可以运行宏来在编译时生成所有代码,然后编译器就可以为所有函数生成代码.

效率/默认值

在最低级别的功能中,我可能不想使用&optional参数.默认调用将从动态绑定中获取值,更糟糕的是,*standard-input*/ *standard-output*可能不是READ-BYTE或为其WRITE-BYTE工作的流.不是在每个实现中都可以使用标准输入/输出流作为二进制流.

LispWorks:

CL-USER 1 > (write-byte 13 *standard-output*)

Error: STREAM:STREAM-WRITE-BYTE is not implemented for this stream type: #<SYSTEM::TERMINAL-STREAM 40E01D110B>
  1 (abort) Return to level 0.
  2 Restart top-level loop.
Run Code Online (Sandbox Code Playgroud)

我也可能想要声明所有生成的函数都是内联的.

类型声明将是另一个需要考虑的事情.

Summmary:不要使用EVAL.