-
테스트 주도 방식으로 리스프 매크로 작성하기Programmer/Programming 2014. 6. 5. 11:34
Lisp에서 매크로를 작성하는 방법을 "Peter Seibel"의 "Practical Common Lisp"에 따르면 다름과 같은 순서로 진행한다.
(비록 쓸모는 없지만 쉽게 이해할 수 있는 두 숫자를 더하는 매크로를 작성하겠다.)- 우선 호출할 예제 폼을 작성한다.
(add 3 5)
- 위 예제 폼으로 확장될 코드를 작성한다.
(+ 3 5)
- 매크로를 구현하다.
(defmacro add (x y) `(+ ,x ,y))
테스트 주도 방식을 적용하면 대략 다음과 같다.
2013/05/07 - [Programming/Project Management] - 테스트 주도 개발 - 둘째날
- 우선 호출할 예제 폼을 작성한다.
- 위 예제 폼으로 확장될 코드를 작성한다.
- 테스트 케이스를 작성한다. (테스트 프레임워크는 생략한다.)
(equal '(+ 3 5) (macroexpand-1 '(add 3 5)))
- 테스트를 통과하는 매크로를 최대한 빨리 개발한다.
위 과정을 계속 반복해나간다.
이번에는 세제곱을 구하는 매크로를 작성해보자.
- 호출할 예제 폼:
(cube (incf x))
- 확장될 코드:
(let ((#:G100 (incf x))) (* #:G100 #:G100 #:G100))
- 위 코드에서 #:G100은 GENSYM 함수의 반환값임을 알 수 있다.
- 테스트 케이스:
- 테스트 케이스의 코드는 다음과 같다.
(equal '(let ((#:G100 (incf x))) (* #:G100 #:G100 #:G100)) (macroexpand-1 '(cube (incf x))))
GENSYM 함수의 반환값은 매번 바뀌기 때문에 CUBE 매크로의 GENSYM 함수의 결과가 #:G100이 되는 것을 보장할 수 없다. *GENSYM-COUNTER* 변수를 이용해서 GENSYM 함수의 매번 달라지는 반환값을 예측가능하게 바꿔보자. 1
이것은 GENSYM 함수는 *GENSYM-COUNTER* 변수를 증가시키며 결과를 생성하기 때문이다.(equal '(let ((#:G100 (incf x))) (* #:G100 #:G100 #:G100)) (let ((*gensym-counter* 100)) (macroexpand-1 '(cube (incf x)))))
GENSYM 함수는 #:G100을 반환하지만, 위 테스트는 항상 실패한다.
이유는 uninterned symbol은 같은 이름이라도 reader는 다른 심볼을 생성하기 때문이다.(eq #:G100 #:G100) ; => NIL
- PRIN1-TO-STRING 함수로 폼을 문자열로 변환하여 비교하여 이 문제를 해결한다.
2
(equal (prin1-to-string '(let ((#:G100 (incf x))) (* #:G100 #:G100 #:G100))) (let ((*gensym-counter* 100)) (prin1-to-string (macroexpand-1 '(cube (incf x))))))
- 테스트 케이스의 코드는 다음과 같다.
- 위 테스트를 통과할 매크로를 구현한다.
(defmacro cube (n) (let ((x (gensym))) `(let ((,x ,n)) (* ,x ,x ,x))))
'Programmer > Programming' 카테고리의 다른 글
리습의 개발 방법을 배우자: 함수와 하위 함수의 개발 (3) 2014.06.24 리습의 개발 방법을 배우자: 대도 웜퍼스 게임의 설계 (0) 2014.06.20 MAPCAR와 그 친구들(MAPC, MAPCAN) (0) 2014.05.30 간단한 데이타베이스를 TDD 로 개발하기 소스 코드 (0) 2014.03.19 간단한 데이타베이스를 TDD 로 개발하기 3회: add-record (0) 2014.03.19 댓글
- 우선 호출할 예제 폼을 작성한다.