Có một số vấn đề với mã này, mặc dù cách tiếp cận chung để có các hàm tạo macro là tốt.
Naming
Các macro không nên được đặt tên là make-...
, bởi vì họ không phải là chức năng mà làm cho một cái gì đó, nhưng macro trong đó xác định một hàm.
thế hệ Mã
Mã EVAL-WHEN ... EVAL
thực sự là xấu và không nên được sử dụng theo cách này.
Cách tốt hơn là viết macro mở rộng thành progn
với định nghĩa hàm.
Nếu tôi muốn sử dụng EVAL
, thì tôi sẽ không cần viết các macro tạo mã, mà chỉ đơn giản là tạo các hàm tạo mã. Nhưng tôi không muốn sử dụng EVAL
, tôi muốn tạo mã cho trình biên dịch trực tiếp. Nếu tôi có các macro tạo mã, thì tôi không cần EVAL
.
EVAL
không phải là một ý tưởng hay vì không rõ mã sẽ được biên dịch - sẽ phụ thuộc vào việc triển khai thực hiện. Ngoài ra việc đánh giá sẽ diễn ra tại thời gian biên dịch và thời gian tải. Nó sẽ là tốt hơn để biên dịch các chức năng tại thời gian biên dịch và chỉ tải chúng tại thời gian tải. Trình biên dịch tệp cũng có thể bỏ lỡ tối ưu hóa có thể cho các hàm được đánh giá.
(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))))
Thay vì EVAL-WHEN ... EVAL
chúng ta định nghĩa một macro khác và sau đó chúng tôi sử dụng nó sau này:
(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))))))
Bây giờ chúng ta có thể sử dụng trên vĩ mô để tạo ra tất cả các chức năng:
(def-reader/writer-functions
("READ" "READ-S" "WRITE")
(nil t)
(1 2 4 8))
Bạn có thể xem phần mở rộng tại đây:
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))
Mỗi dạng con sau đó sẽ được mở rộng thành các định nghĩa hàm.
Bằng cách này trình biên dịch chạy các macro để tạo tất cả mã tại thời gian biên dịch và sau đó trình biên dịch có thể tạo mã cho tất cả các hàm.
Efficiency/Defaults
Trong một chức năng cấp thấp nhất tôi có thể không muốn sử dụng một tham số &optional
. Cuộc gọi mặc định sẽ nhận giá trị từ liên kết động và, tệ hơn, *standard-input*
/*standard-output*
có thể không phải là luồng mà READ-BYTE
hoặc WRITE-BYTE
hoạt động. Không phải trong mọi triển khai, bạn có thể sử dụng luồng đầu vào/đầu ra tiêu chuẩn làm luồng nhị phân.
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.
tôi cũng có thể muốn khai báo tất cả các chức năng được tạo ra để được inlined.
Tuyên bố loại sẽ là một điều khác cần suy nghĩ.
Summmary: không sử dụng EVAL.
Tại sao không sử dụng '& tùy chọn'? Nếu nó cho hiệu quả, nó vẫn áp dụng nếu các chức năng được inlined? – nisekgao
@nisekgao: xem chỉnh sửa của tôi. –