首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在通用Lisp中编写类似的函数?

如何在通用Lisp中编写类似的函数?
EN

Stack Overflow用户
提问于 2017-01-01 11:00:11
回答 2查看 290关注 0票数 8

我正在从实用通用Lisp学习Common。它在第24章中有一个读取和写入二进制文件的辅助函数示例。以下是一个例子:

代码语言:javascript
复制
(defun read-u2 (in)
  (+ (* (read-byte in) 256) (read-byte in)))

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

代码语言:javascript
复制
(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))))))

它起作用了。它生成读写大小为1、2、4和8的无符号整数和有符号整数的函数。但我想知道是否有更好的方法。

在通用Lisp中编写一组类似函数的最佳方法是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-01-01 11:47:25

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

命名

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

码生成

EVAL-WHEN ... EVAL代码非常糟糕,不应该以这种方式使用。

更好的方法是编写带有函数定义的扩展为progn的宏。

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

EVAL不是一个好主意,因为不清楚代码是否会被编译--这将取决于实现。此外,评估将在编译时和加载时进行。最好是在编译时编译函数,并且只在加载时加载它们。文件编译器还可能会忽略对计算函数的优化。

代码语言:javascript
复制
(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))))

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

代码语言:javascript
复制
(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))))))

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

代码语言:javascript
复制
(def-reader/writer-functions
 ("READ" "READ-S" "WRITE")
 (nil t)
 (1 2 4 8))

你可以在这里看到扩张:

代码语言:javascript
复制
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))

然后,每个子表单将被扩展到函数定义中。

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

效率/默认

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

LispWorks:

代码语言:javascript
复制
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.

我还可能希望声明所有生成的函数都是内联的。

类型声明将是另一件需要考虑的事情。

:不要使用EVAL。

票数 9
EN

Stack Overflow用户

发布于 2017-01-03 23:45:36

通常,我更愿意将要读取的字节数作为另一个参数添加到函数中:

代码语言:javascript
复制
(defun read-integer (stream bytes)
  (check-type bytes (integer 1 *))
  (loop :repeat bytes
        :for b := (read-byte stream)
        :for n := b :then (+ (* n 256) b)
        :finally (return n)))

签名和endianness可以作为关键字参数添加。这种编程方式对于易懂的代码是很好的,这些代码也可以很容易地通过像SLIME这样的工具导航。

通过宏展开这是一种有效的优化策略,我遵从雷纳的回答

在从流读取数字的特定情况下,优化从一开始就可能是一个有效的目标,因为这往往会在紧循环中得到大量使用。

但是,如果您这样做,您还应该彻底记录生成的内容。如果代码的读者看到一个操作符read8bes,他很难找到定义它的位置。你得帮帮他。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41414966

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档