2017-01-01 1 views
8

나는 Common Lisp을 Practical Common Lisp에서 배우고있다.Common Lisp에서 비슷한 기능을 작성하는 방법은 무엇입니까?

(defun read-u2 (in) 
    (+ (* (read-byte in) 256) (read-byte in))) 

내가 마찬가지로 이진수의 다른 종류를 읽는 기능을 쓸 수 있습니다 : 그것은 읽고 여기에 하나의 예제 제 24 장에서 바이너리 파일을 작성하기위한 헬퍼 함수의 예를 가지고있다. 그러나 그렇게하는 것은 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)))))) 

작동합니다. 1, 2, 4, 8 크기의 부호없는 부호있는 정수를 읽고 쓸 수있는 함수를 생성합니다. SLIME은 그것을 이해합니다. 그러나 더 좋은 방법이 있는지 궁금합니다.

Common Lisp에서 비슷한 기능을 많이 작성하는 가장 좋은 방법은 무엇입니까?

답변

9

이 코드에는 몇 가지 문제가 있지만 기능을 생성하는 매크로를 작성하는 일반적인 방법이 좋습니다. 그들이 함수를 정의 무언가를 만드는 기능을하지만, 매크로가되지 않기 때문에

명명 매크로는 make-... 이름되어서는 안된다.

코드 생성

EVAL-WHEN ... EVAL 코드는 정말 나쁜이 방법을 사용할 수 없습니다.

더 좋은 방법은 함수 정의로 progn으로 확장되는 매크로를 작성하는 것입니다.

EVAL을 사용하려면 매크로를 생성하는 코드를 작성하지 않아도되지만 코드 생성 기능 만 사용하면됩니다. 하지만 EVAL을 사용하고 싶지는 않습니다. 직접 컴파일러 용 코드를 만들고 싶습니다. 매크로를 생성하는 코드가 있다면 EVAL이 필요하지 않습니다.

EVAL은 코드가 컴파일 될지 확실하지 않기 때문에 바람직하지 않습니다. 구현에 따라 달라질 수 있습니다. 또한 평가는 컴파일 시간 및로드 시간에 수행됩니다. 컴파일 타임에 함수를 컴파일하고로드시에만로드하는 것이 좋습니다. 파일 컴파일러는 평가 된 함수에 대한 가능한 최적화를 놓칠 수도 있습니다. 대신 EVAL-WHEN ... 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)))) 

우리는 또 다른 매크로를 정의하고 우리는 나중에 사용

(def-reader/writer-functions 
("READ" "READ-S" "WRITE") 
(nil t) 
(1 2 4 8)) 

당신은 할 수 있습니다

(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)))))) 

이제 우리는 모든 기능을 생성하는 매크로 위에 사용할 수 있습니다 여기에서 확장을 참조하십시오 :

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)) 

그런 다음 각 하위 양식이 함수 정의로 확장됩니다.

이 방법으로 컴파일러는 컴파일 타임에 모든 코드를 생성하기 위해 매크로를 실행하고 컴파일러는 모든 함수에 대한 코드를 생성 할 수 있습니다.

효율/기본 I는 &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. 

또한 인라인 될 생성 된 모든 함수를 선언 할 수 있습니다.

유형 선언은 생각할 또 다른 것입니다.

어머 니스트 : EVAL을 사용하지 마십시오.

+0

왜 '& optional'을 사용하지 않으시겠습니까? 효율성을 위해 필요한 경우 함수가 인라인 된 경우에도 적용됩니까? – nisekgao

+0

@nisekgao : 내 편집 참조. –

2

일반적으로, 난 그냥 함수에 다른 매개 변수로 읽을 바이트 수를 추가하는 것을 선호 것 :

(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))) 

되는 Signedness 및 엔디안은 키워드 인수로 추가 할 수 있습니다. 이 프로그래밍 방식은 SLIME과 같은 도구를 통해 쉽게 탐색 할 수있는 이해하기 쉬운 코드에 유용합니다.

매크로를 통해 풀어주는 것이 유효한 최적화 전략이며, 나는 Rainer's answer으로 연기합니다.

스트림에서 숫자를 읽는 특별한 경우에는 최적화가 타이트한 루프에서 많이 사용되기 때문에 처음부터 유효한 목표 일 가능성이 높습니다.

그러나 이렇게하면 생성되는 내용을 철저히 문서화해야합니다. 코드 판독기에 연산자 read8bes이 있으면 정의 된 위치를 쉽게 찾을 수 없습니다. 그를 도울 필요가있어.

+1

이와 같은 일반 함수의 경우 8 비트 바이트 가정을 문서화해야합니다. ;-) –

관련 문제