[해커 최후의 언어, 커먼 Lisp] ① 왜 해커의 언어일까?

일반입력 :2008/09/16 15:49

최종원 (ITSEC 대표)

LISP는 1958년 존 맥카시에 의해 처음으로 구현되기 시작했으며, 특히 인공지능 분야에서 흔히 사용되고 있는 언어이다. 역사가 오래된 프로그래밍 언어라고 해서 시대에 뒤쳐졌을 거라고 생각한다면 오산이다. LISP는 특유의 기민성으로 특히 오늘날과 같이 경쟁적으로 제품이나 서비스를 완성해야 하는 현실에서는 다른 어떠한 프로그래밍 언어보다도 빠르고 적절한 프로그래밍이 가능토록 도와준다. 현대적 LISP 표준인 커먼 LISP(Common LISP, 이하 CL)는 특히 어렵고 도전적인 분야에서 많이 사용되고 있는데, 독자들을 이런 기민한 CL 프로그래밍 세계로 초대하려고 한다.

‘성당과 시장’으로 잘 알려진 에릭 레이먼드(Eric Raymond)는 ‘해커가 되는 방법(How to become a hacker)’에서 LISP에 대해 다음과 같이 이야기하고 있다.

<생략> - 다른 프로그래밍 언어의 중요성에 대한 이야기들

Lisp는 (위에서 언급한 프로그래밍 언어들과는) 다른 이유 - 즉 그 언어에 정통하게 될 때 얻는 심오한 깨달음의 경험 - 에서 배울 만한 가치가 있다. 이런 경험을 통해 실제로는 Lisp를 많이 사용치 못한다 하더라도 남은 생애 동안 여러분은 훌륭한 프로그래머로 거듭나게 될 것이다.

에릭 레이먼드가 말하는 심오한 깨달음의 경험이란 무엇일까? 추측컨대 그것은 Lisp가 갖는 메타 순환적인 특징, 즉 코드와 데이터의 구조가 같기 때문에 경험할 수 있는 ‘프로그래밍 언어 자체에 대한 프로그래밍’일 것이다. 지난 수십 년간 다른 프로그래밍 언어에서 Lisp의 여러 특징을 모방해왔지만, 아직 Lisp의 앞서 말한 특징을 제대로 구현하거나 모방하고 있는 프로그래밍 언어는 존재하지 않는다.

어느 정도 수준에 이른 Lisp 프로그래머의 프로그래밍 방법은 자신이 해결해야 할 문제를 효과적으로 기술할 수 있는 프로그래밍 언어를 만든 후 문제를 공략하는 것이다. 이런 방식의 프로그래밍은 개발자로 하여금 자신의 생각을 충분히 높은 층에서 추상화해 생각할 수 있게 해주며, 그 생각을 거의 여과 없이 가상의 확장 언어로 기술한 후 가상의 언어와 Lisp 사이의 차이를 메워서 완성하는 형태로 설명할 수 있다.

문제 영역을 기술하기 위한 확장 언어를 개발하면서 문제 자체를 한층 더 깊이 있게 이해할 수 있으며, 확장 언어를 통해 문제 영역에 대한 프로그래밍하기 때문에 집중적이고 기민하게 문제를 해결 할 수 있다. 프로그래밍 언어 자체에 대한 프로그래밍이라는 Lisp의 특징은 자유로운 프로그래밍 스타일을 원하는 해커들의 요구를 잘 충족시켜 준다는 것도 부정할 수 없는 사실이다.

커먼 리습 개발 환경

주위에서 CL을 배우기 시작하는 사람들을 관찰한 후 얻은 결론 중 하나는 CL의 개발 환경은 프로그래밍 언어 그 자체만큼 중요한 데도 불구하고 흔히 간과된다는 사실이다. CL 시스템에 대해 들을 수 있었던 불만들 중 대부분이 개발 환경에 대한 이해 부족에 기인한 것들이었다. CL 프로그래밍을 배우고 익히는 과정의 30% 정도는 개발 환경에 대한 것임을 잊지 말고 명심하기 바란다.

Lisp는 처음에는 인터프리터 형태로 존재하기 시작해서 현재는 상호작용 시스템의 형태로 발전했다. 특히 CL의 상호작용 시스템은 Lisp 리스너(listener), 에디터, 인터프리터, 컴파일러, 디버거 등이 유기적으로 결합되어 사용자와 대화하듯 하면서 프로그램을 개발할 수 있도록 되어 있다. 이런 상호작용 환경에서 다음과 비슷한 주기로 프로그램을 작성하는 것이 보통이다.

[1] 에디터에서 프로그램의 일부 - 이를 테면 함수 하나 - 를 작성한다.

[2] 리스너에서 작성한 함수 하나를 평가한다.

[3] 리스너에서 함수에 인자를 주고 호출해 함수가 제대로 작동하는지 확인한다.

[4] 리스너에서 예상치 못한 에러를 만났을 경우 자동으로 활성화되는 디버거를 통해 문제를 파악하고 해결한다.

[5] 디버거에서 문제를 파악했다면 에디터로 돌아와서 그 문제를 수정하고 다시 [1]~[4]의 과정을 통해 확실히 문제를 해결한 후 프로그램의 일부 혹은 전부를 컴파일해서 메모리에 컴파일된 형태로 존재하도록 한다.

[6] 에디터에서 필요에 따라 파일을 저장하거나 컴파일한다.

[7] [1]로 돌아가거나 프로그래밍을 마친다.

앞의 일들을 제대로 할 수 있도록 도와주는 개발 환경이 되기 위해서는 에디터와 Lisp 리스너가 서로 연결되어 필요한 정보들을 주고받을 수 있어야 한다. 상업용 CL의 경우 앞의 일들을 자연스럽게 할 수 있는 환경을 제공하지만, 공개 소프트웨어 CL들은 그렇지 않다. 그렇다고 공개 소프트웨어 CL을 사용하는 사람은 앞과 같은 개발 주기로 프로그래밍할 수 없다는 이야기는 아니다.

Emacs 에디터로 프로그래밍 즐기기

리습 프로그래머들에게 에디터는 Emacs 오직 하나만이 존재한다. 상업용 CL들은 개발 환경의 일부로 Emacs와 유사한 에디터를 제공하며, 공개 소프트웨어 CL들은 Emacs와 CL 시스템이 상호작용하도록 Emacs Lisp(Elisp)으로 작성한 프로그램인 ILISP(http://sourceforge.net/projects/ilisp)이나 Superior Lisp Interaction Mode for Emacs (http://www.common-lisp.net/project/slime/, 이하 SLIME)과 같은 패키지를 이용하면 앞에서 언급한 개발 환경을 갖출 수 있다. 이런 이유 때문에 Emacs 없는 CL 프로그래밍이란 상상하기 어렵다.

다른 에디터를 이용해서 CL 프로그래밍하는 것도 불가능한 것은 아니지만, 그런 사람들은 얼마가지 못해서 CL 프로그래밍을 포기하게 된다. F1 레이싱 카를 운전하기 위해서 적절한 환경 - 이를 테면 잘 교육받은 메카닉 팀 - 이 필요한 것과 같은 이치이다(물론 그런 것을 모두 갖추었다 하더라도 경주에 이긴다는 절대적인 보장은 없다!).

CL을 진지하게 배우고자 하는 사람은 Emacs를 사용해야 한다. 처음에는 약간 어려울 지도 모르지만, 하루에 조금씩 익혀나가면 한달 이내에 불편 없이 사용할 정도는 되리라고 믿는다. Emacs를 결코 익힐 마음은 없지만 CL 프로그래밍을 하겠다는 사람은 최후의 선택으로 상업용 CL을 구입할 수 있을 것이다. 상업용 CL의 내장 에디터는 Emacs에 비해 기능이 적기 때문에 설치하는 데 있어서 어려움도 거의 없다. 하지만, 계속 CL 프로그래밍 하다보면 언젠가는 Emacs를 사용해야 할 날이 올 것이라 장담한다!

Emacs를 사용하고 있거나 익히기로 결심한 사람이라면, CL 프로그래밍에 사용할 패키지인 ILISP이나 SLIME을 설치해야 한다. ILISP은 오랫동안 단 하나뿐인 CL 프로그래밍용 Emacs 패키지로 존재했었지만, 코드의 질이 저하되어 더 이상 원활한 유지보수가 이뤄지지 않고 있으며, SLIME은 ILISP을 대치하겠다는 야심(?)으로 비교적 최근에 시작되어 개발이 활발히 진행되고 있다.

필자의 팀은 상업용 CL과 공개 소프트웨어 CL을 사용하면서 팀내 모든 개발자들이 Emacs + SLIME 이라는 동일한 개발 환경을 사용하고 있다. 필자의 팀에서 전용 개발 환경이 제공되는 상업용 CL을 이용해 프로그래밍할 때도 전용 개발 환경을 사용하지 않는 이유는 특정 CL로부터 독립적인 개발 환경을 원하기 때문이다. 이렇게 되면 CL의 구현을 바꾸더라도 이전과 다름없는 생산성을 유지할 수 있다. 물론 때때로 GUI, 프로파일 등이 필요한 경우 전용 개발 환경을 이용하기도 하지만 다행히도 그런 일들은 자주 일어나지 않는다. 국내에도 Emacs 관련 사이트(http://emacs.kldp.org/wiki/wiki.php)가 있으므로 Emacs를 배울 때 적극 활용하기 바란다.

Emacs와 SLIME을 설치했다면, 다음 예와 비슷하게 .emacs 설정 파일을 편집한다. 구체적인 디렉토리나 경로는 다를 수 있으므로 프로그램이나 데이터 설치 후 확인하여 수정해야 할 것이다. 개발 환경에서 CLHS를 SLIME의 단축키를 통해 문맥(context)에 맞게 참조하기 위해서는 “common-lisp-hyperspec-root”를 CLHS의 루트 디렉토리로 설정해야 한다. “inferior-lisp-program”은 자신이 사용할 CL 구현 실행 파일로 설정해야 한다.

(custom-set-variables

;; 여러 가지 색깔을 이용하여 프로그래밍이 용이하도록 폰트락 모드를 활성화한다.

'(global-font-lock-mode t nil (font-lock)))

;; 괄호의 열고 닫음을 눈으로 확인할 수 있게 해주는 Lisp 프로그래밍에 필수적인 모드

(show-paren-mode)

;;; Slime 셋업

(add-to-list 'load-path ~/dev-tools/site-lisp/slime)

(require 'slime)

(add-hook 'lisp-mode-hook (lambda () (slime-mode t)))

(add-hook 'inferior-lisp-mode-hook (lambda () (inferior-slime-mode t)))

;;; Apple Darwin ?

(defun powerpc-apple-darwin? ()

(search powerpc-apple-darwin system-configuration))

;;; Intel Linux ?

(defun i386-linux? ()

(and (search i386 system-configuration)

(search linux system-configuration)))

;;; Platform dependent setup - CLHS와 SBCL

(cond ((i386-linux?)

(setq common-lisp-hyperspec-root file:/usr/share/doc/hyperspec/

inferior-lisp-program /usr/local/bin/sbcl))

((powerpc-apple-darwin?)

(setq common-lisp-hyperspec-root file:////Applications/MCL/HyperSpec-6-0 Folder/HyperSpec/

browse-url-browser-function

(lambda (url &optional new-window)

(message url)

(do-applescript (concat tell application Safari to open location

url )))

inferior-lisp-program /usr/local/bin/sbcl)))

(add-hook 'lisp-mode-hook

(lambda ()

(set (make-local-variable 'lisp-indent-function) 'common-lisp-indent-function)))

커먼 리습 구현

전형적인 CL 구현은 ANSI 표준에서 정의된 부분과 그렇지 않은 부분으로 이뤄진다. 대개의 경우 표준 부분은 CLHS를, 비표준 부분은 각 CL의 문서를 참고해 파악해야 한다. 비표준 부분에 대해서는 적절한 문서가 없는 경우도 있는데, 이런 경우에는 해당 CL의 사용자 그룹이나 메일링 리스트를 통해 도움을 받는 것이 보통이다.

표준은 아니지만 대부분의 CL에서 지원함으로써 사실상의 표준으로 취급하는 것도 있는데, 예를 들면 MetaObject Protocol(이하 MOP)이 그러하다. MOP은 Aspect Programming, AspectJ 등으로 널리 알려진 Gregor Kiczales를 주축으로 제안되었던 CLOS의 확장이다. MOP은 표준이 아니기 때문에 사용하고자 하는 사람은 Gregor Kiczales의 ‘The Art of Metaobject Protocol(이하 AMOP)’을 참고해야 한다.

비표준인 부분은 대개의 경우 계층화된 별도의 소프트웨어(layered product)인 경우와 하위 수준 구현에 의존성이 있는 경우가 빈번하다. 전자는 다른 프로그래밍 언어에서는 라이브러리 형태와 유사하며, 후자는 CL을 구현한 개발자나 회사가 추후 변경을 위해 일반 프로그래머들은 사용하지 않았으면 하는 경우가 대부분이다.

CL 시스템(앞으로 CL 프로그래밍 언어 자체뿐 아니라 CL 구현들을 ‘CL 프로그래밍 언어 시스템’ 혹은 간단히 ‘CL 시스템’이라고 칭한다)은 오랜 시간동안 쉼 없이 꾸준히 변화해 왔기 때문에 지금은 쓸만한 시스템들이 꽤 존재한다. 윈도우, 리눅스, 맥, 유닉스 등에서 동작하는 상업용 및 공개 소프트웨어 CL 시스템들이 있다. 상업용 CL 시스템들은 대부분 윈도우, 리눅스, 맥, 유닉스 등의 OS에서 공개 소프트웨어 CL 시스템들은 그들 중 일부에서 동작하는 것이 보통이다. 공개 소프트웨어 CL 시스템 중 몇몇(예를 들면 맥용인 OpenMCL)은 상업용에 버금가는 질을 유지하고 있다고 한다.

상업용 CL 시스템으로는 Digitool의 Macintosh Common Lisp, Franz의 Allegro Common Lisp이나 Xanalys의 LispWorks(이하 LW) 등이 가장 널리 사용되고 있다. 공개 소프트웨어 CL로는 CLISP, Carnegie Mellon University Common Lisp(이하 CMUCL), OpenMCL, Steel Bank Common Lisp(이하 SBCL) 등이 있으며, OpenMCL은 맥에서, 나머지는 리눅스나 유닉스 등에서 동작한다. 그 외에도 몇 개의 구현이 존재하지만 질적으로 떨어지는 것들이 대부분이다. Cliki(http://www.cliki.net/Common%20Lisp%20implementation)에서 많이 사용되고 있는 CL 구현에 대해 보다 자세히 알 수 있다.

상업용과 오픈 소프트웨어 CL 시스템 사이의 가장 큰 차이는 사용자에 대한 배려와 지원이다. 상업용은 경고나 에러 메시지, 디버깅 등에 필요한 내용 등이 오픈소스에 비해 자세하고 친절하기 때문에 불필요한 시간 낭비를 최소화해 준다. 또 직접 혹은 간접적인 지원으로 개발자가 자신이 처한 문제 자체에만 집중할 수 있도록 도와준다. 비상업용인 경우에는 종종 자신의 문제가 아닌 CL 시스템 자체 문제에 대해 스스로 해결책을 찾아내야 한다. 필자의 경우, 이전에 복잡한 시스템을 개발할 당시 여러 번 빠르고 정확한 기술지원을 경험했기 때문에 당연히 상업용을 선호하지만, 때때로 경제적인 이유에서 어쩔 수 없이 비상업용을 사용해야 할 경우도 있는 것이 현실이다.

앞에서 나열한 상업용 CL 시스템들은 대체로 모두 성능과 지원이 좋은 것으로 알려져 있다. 비상업용의 경우 CMUCL이 수학 분야에서의 최적화는 다른 어떤 CL보다 뛰어난 것으로 알려져 있고, SBCL은 CMUCL을 바탕으로 더 나은 코드의 질을 목표로 개발되고 있으며, OpenMCL은 맥에서 독보적인 존재이며, CLISP은 사용하기에 무난한 것으로 알려져 있다. 필자가 느끼기에 개발사나 사용자들의 활동이 활발한 CL 시스템들은 Allegro Common Lisp, LW, OpenMCL, SBCL 등이다. 필자의 팀에서는 하나의 CL에 의존하지 않기 위해서 LW와 SBCL을 개발용 CL로 사용하고 있다. 선택 기준은 LW의 경우 비교적 저렴한 가격과 빠르고 친절한 지원이고, SBCL은 공개 소프트웨어 CL로서의 발전 가능성이다(아마도 SBCL이 아니었다면 CMUCL을 선택했을 것이다).

앞으로 다룰 예들은 필자의 팀에서 개발 중인 상업용 서비스의 일부로서 데비안 리눅스, LW와 SBCL, 아파치 웹 서버 등을 사용해 개발 중인 것이다. 코드 자체는 최대한 표준 CL만을 사용하려고 하였지만, 어쩔 수 없이 Multi Process(혹은 쓰레드), 외부 함수 호출부(Foreign Function Interface), 논리적 경로명 등과 같은 하위 층에 의존성 있는 비표준 부분을 사용하는 경우가 발생했다. 다른 구성으로 동작하게 하는 일이 불가능하지는 않지만, 쓸데없는 시간낭비를 줄이기 위해서 독자는 리눅스, LW 혹은 SBCL, 아파치 등과 유사한 구성을 갖출 것을 권한다.

CL 시스템 설치

CL 시스템 설치에 대해서는 자신이 사용할 CL 시스템의 문서를 주로 참조해야 한다. 그 외에도 한국Lisp사용자모임 등을 통해 도움을 받을 수도 있을 것이다. LW의 경우 구입시 함께 오는 설명서를 참조해 설치하면 되고, 문제가 발생한다 하더라도 판매처로부터 지원을 받을 수 있으므로 별다른 어려움이 없을 것이다. 필자가 개발에 사용하는 버전은 4.3+ 패치 버전인 4.3.7이다.

SBCL은 개발 사이트(http://sourceforge.net/projects/sbcl)에서 다운받아 설치할 수 있다. 설치시 주의사항은 이미 자신의 시스템이 SBCL을 컴파일할 수 있는 CL이 없는 경우에는 바이너리를 다운받아야 한다는 점이다(SBCL 소스코드는 CL로 컴파일한다). SBCL은 계속 개발이 진행 중이기 때문에 버전에 따라 상이한 결과를 얻는 경우가 있을 수 있다. 필자의 팀에서는 외부 함수 호출부의 라이브러리 로딩 부분이 버전마다 달라서 문제가 된 경우가 있었고, 알 수 없는 원인으로 일부 기능이 제외된 채 빌드된 것을 모르고 반나절을 고생한 경험이 있다. 필자의 팀에서는 SBCL 버전 0.8.10을 사용하다가 얼마 전 0.8.13으로 업그레이드했다. LW 혹은 SBCL을 설치한 후에는 초기화 파일을 다음 예를 참조해 설정한다.

;;;; -*- Mode: lisp; Package: cl-user; Syntax: Common-lisp -*-

;;

;; Copyright (C) 2004 Jong-won Choi, Jiwon Seo, and Young-hoo Kim

;; All rights reserved.

;;

;; Author: $Author: kmp $

;; Version: $Id: .sbclrc,v 1.21 2004/07/26 01:11:04 kmp Exp $

(in-package :cl-user)

;;; NOTE: 경로명을 적절하게 바꿀 것

(require asdf (merge-pathnames kmp/cclan/asdf/asdf (user-homedir-pathname)))

;;; 주어진 경로명이 파일 이름이면 참(T), 아니면 거짓(NIL)을 리턴하는 함수

(defun file? (pathname)

(let ((string-name (namestring pathname)))

(if (char= #/ (aref string-name (1- (length string-name))))

nil

t)))

;;; ASDF 사용을 위한 유틸리티 함수

;;; 주어진 경로에서 ASDF 시스템 정의 파일을 발견하면 ASDF에 등록하고

;;; (*central-registry*), 서브 디렉토리에 대해 재귀적으로 ASDF에 등록 시도

(defun build-asdf-central-registry (path)

(unless (file? path)

(let ((asd-file (directory (merge-pathnames *.asd path))))

(when asd-file

(push path asdf:*central-registry*))

(let ((sub-directories (directory (merge-pathnames *.* path))))

(when sub-directories

(map nil #'build-asdf-central-registry sub-directories))))))

;; local 개발 디렉토리인 ~/kmp에 대해 재귀적으로 ASDF 등록

(build-asdf-central-registry (merge-pathnames kmp/ (user-homedir-pathname)))

Emacs와 CL 시스템의 설정을 마친 후 Emacs를 실행시키고, ‘M-x slime’을 통해 CL 시스템을 시작해서 앞으로 다음 연재까지 1개월간 CL 프로그래밍 연습을 해두기 바란다.

커먼 리습을 위한 라이브러리

소프트웨어를 개발하는데 모든 것을 자체 개발하는 경우는 더 이상 찾아보기 힘들다. 이 같은 추세는 CL 프로그래밍에서도 예외는 아니다. CL의 라이브러리 모델은 간단하다. 최종 제품으로써의 소프트웨어는 항상 CL 시스템을 포함한다. 원한다면 최종 제품에 컴파일러를 끼워넣어 런타임에 사용할 수도 있다(상업용 CL 시스템은 다른 프로그래밍 언어 시스템보다 가격이 비싼 이유 중 하나이다). 개발 중인 시스템에서 표준 이외의 라이브러리를 사용한다면 원하는 라이브러리를 CL 시스템으로 로드하면 된다.

잠깐 주제를 벗어나서 CL 시스템에서 어떻게 최종적인 제품을 제작하는지 살펴보기로 하자. 개발하는 동안은 상업용이나 공개 소프트웨어 CL 시스템이나 거의 유사하다. 소스코드를 모두 컴파일하고 로드한 후 메인 함수를 실행하면 애플리케이션이 동작하기 시작한다. 개발을 끝마치면 컴파일된 파일들을 순서대로 로드한 후 메인 함수를 호출하는 CL 소스코드 파일을 하나 작성해 컴파일된 파일들과 함께 배포하는 것이 보통이다. 하지만 때때로 하나의 바이너리 이미지 파일 형태로 배포할 필요가 있다. 공개 소프트웨어 CL 시스템은 이 부분에 대한 특별한 과정이나 기능이 없다. 상업용 CL 시스템은 이와는 달리 딜리버리(delivery)라는 과정이 있어서 완성된 프로그램을 최적화하여 사용하지 않는 라이브러리 함수들을 제거하고 호출할 메인 함수를 셋팅해서 바로 실행 가능한 하나의 바이너리 파일을 만들어 내는 것이 가능하다.

필자의 팀에서 개발하는 제품은 몇 가지 CL용 공개 소프트웨어 라이브러리를 사용한다. 가장 중요하다고 꼽을 수 있는 것들이 아파치 웹 서버 뒤에서 모든 일을 하는 CL 웹 서버를 쉽게 만들 수 있도록 해주는 TBNL과 CL에서 RDB를 사용할 수 있게 해주는 CLSQL이다. 둘 다 활발히 개발되고 있으며, 소스코드의 질도 꽤 높은 편이다. TBNL은 http://www.weitz.de/tbnl/에서, CLSQL은 http://clsql.b9.com/에서 다운로드하거나 참고자료를 읽을 수 있다.

프로그래밍 계획

연재를 통해 다룰 CL 프로그래밍 예를 설명하기 위해서는 현재 필자의 팀에서 수행하고 있는 프로젝트에 대한 설명이 약간 필요하다. 현재 개발 중인 시스템은 ‘프로젝트 KMP’로, 웹 기반 ‘IT 분야 지식 거래 커뮤니티’ 서비스용 시스템이다. KMP는 웹 서브시스템, RDB 서브시스템, 그리고 서비스의 중심 역할인 KMP 객체층 등으로 이뤄져 있다. 웹은 KMP 객체들을 위한 프리젠테이션 계층이고, RDB는 KMP 객체들에게 영속성을 부여하기 위한 계층이다.

사용자가 웹을 통해 KMP 시스템에 접근하면 RDB로부터 필요한 객체들을 꺼내서 세션(session) 층에 두고 사용자가 로그아웃 혹은 세션 타임아웃될 때까지 객체들을 조작할 수 있다. 주된 조작들은 질문과 같은 요청, 요청에 대한 대답, 요청과 대답에 참여한 사용자 사이의 평가, 그리고 객체 사이의 이동 및 검색 등이다. KMP 시스템을 개발하면서 목표가 여러 가지 있지만, 특히 다음과 같은 사항을 충족시키기 위해 노력했다.

◆ 손쉽게 재사용할 수 있을 것

◆ 새로운 기능을 빨리 추가할 수 있을 것

◆ 유지보수가 쉬울 것

◆ 임시방편보다는 근본적인 문제를 해결할 것

◆ 외부에서 개발된 소스코드는 원칙적으로 수정하지 않을 것

그 결과 KMP 웹 프로그래밍을 위한 프레임워크를 작성하게 되었고, 웹 프레임워크를 통해 비교적 일관성 있는 웹 프로그래밍 할 수 있게 되었다. 또 RDB를 이용한 ORDB 확장을 통해 KMP 시스템 프로그래머가 KMP 시스템 내의 객체에만 집중할 수 있도록 하였다.

웹 프레임워크

웹 프레임워크를 갖고 있다 하더라도 쉽게 웹 프로그래밍하기는 어려운데, 그 이유는 웹 애플리케이션 자체의 로직과 HTML 브라우저를 통해 보여줄 프리젠테이션이 쉽게 분리되지 않는 데 기인한다. 이러한 이유는 CSS(Cascade Style Sheet), HTML 등의 표준을 정확히 따르는 브라우저가 없기 때문이라고 생각한다. 단편적인 예로 브라우저들이 CSS 표준을 잘 지킨다면 프리젠테이션에 필요한 세세한 정보들을 CSS로 분리할 수 있을 법도 한데, 실제로는 그러한 브라우저가 존재하지 않는다.

결국 수많은 웹 사이트들이 테이블에만 의지해 HTML 요소들을 배치하는 데는 그럴만한 이유가 있었다. 필자의 팀에서는 모질라, 인터넷 익스플로러, 사파리 등의 웹 브라우저에서 실험해 본 후 로직과 프리젠테이션의 완벽한 분리는 포기하게 되었다. KMP 시스템에서 사용할 목적으로 개발한 웹 프레임워크는 다음과 같은 것들을 제공해 주며, 주로 CL의 매크로, CLOS 등을 이용해 개발하게 될 것이다.

◆ 웹 사이트 객체의 정의. 즉 웹 사이트 전체에 대해 일관성 있게 유지될 내용들을 정의한다. 이에 속하는 것으로는 시작 URL(URL prefix), HTML 프롤로그(prologue), 문자셋(character set), 각종 디렉토리 및 파일 정보(CSS, 이미지, 자바스크립트 등), 메뉴 정보, 헤더를 만들어 주는 함수, 좌우측에 사이트에 관련된 HTML을 만드는 방법 등

◆ 웹 페이지 객체의 정의. 페이지마다 제목, 애플리케이션의 로직을 갖고 있는 액션 함수(action function), 페이지의 HTML을 만들어 주는 함수, 좌우측에 페이지에 관련된 HTML을 만드는 방법, 현재 페이지로부터 다음 페이지로 이동하는 규칙 등

◆ 브라우저의 요청으로부터 웹 사이트와 웹 페이지를 얻어내어 액션 함수를 수행한 후 그 결과에 따라 HTML을 만들어내는 뷰(view) 함수들을 차례로 호출하는 중앙 프로세서

ORDB 확장

한편, CL에서 RDB를 ORDB로 사용하기 위해서는 CLOS의 MOP을 이용해야 하기 때문에 웹 프레임워크보다 약간 더 CL에 대해 알아야 한다. MOP은 CLOS 구현 자체에 대한 프로그래밍을 가능케 하는데, MOP을 이용하면 CLOS 구성요소들이 디폴트로 갖고 있는 의미를 바꾸거나 존재하지 않는 구성요소들을 새로 정의해 CLOS 자체를 확장할 수 있다. MOP에 대해서는 AMOP이 유일한 참고서이다. MOP을 공부하려면 먼저 CLOS에 대한 이해가 필요하므로 Sonya E. Keene의 책을 공부한 후 AMOP을 공부하거나 참고서로 삼는 것이 좋다.

KMP 시스템은 RDB를 사용하기 위해서 CLSQL을 사용한다. CLSQL을 간단히 소개하자면 CL에서 RDB를 사용할 수 있도록 해주는 드라이버, CL용 인터페이스 함수들, CL용 인터페이스 등의 집합이다. CLSQL은 다양한 RDB를 동일한 인터페이스로 지원하기 위해서 각 RDB만의 비호환 기능은 사용하지 않는다. KMP 시스템의 경우 RDB로 PostgreSql만을 사용하기로 결정했기 때문에 이런 제약은 오히려 방해가 된다. 특히 CLSQL에서 제공하는 API만으로는 PostgreSql의 상속, many-to-many의 표현 등에 대해서 RDB의 테이블 사이의 관계와 객체 사이의 관계가 결코 적절하게 유지될 수 없었다.

ORDB 확장 이후 프로그래머는 보통의 CLOS 프로그래밍과 마찬가지로 객체들을 만들고, 각 객체에 대해 ‘update-record-from-instance’를 이용해 RDB에 객체를 저장할 수 있으며, ‘select’를 이용해 RDB로부터 객체를 불러낼 수 있다.

(defvar *toplevel-category*) ; 상위 레벨 분류 객체를 갖게 될 변수

(setq *toplevel-category* (make-category All nil nil nil)) ; CLOS 층에서 분류 객체 생성

(update-record-from-instance *toplevel-category*) ; 객체를 RDB에 저장

...

;; RDB로부터 객체를 불러와서 다시 변수와 연결한다.

(setq *toplevel-category*

(first (select 'category

:where [= [slot-value 'category 'name] All] :flatp t :refresh t)))

KMP 시스템의 모든 객체들은 영속성 객체의 id(Persistent Object ID : POID)를 갖게 될 것이므로 앞의 코드에서 마지막 줄은 다음과 같이 바뀔 수 있을 것이다.

(setq *toplevel-category* (find-persistent-object (poid *toplevel-category*) :refresh t))

ORDB 확장은 MOP을 이용해 기존의 CLSQL에 대한 수정을 최소로 하면서 원하는 확장을 시도할 계획이다.

SW 개발의 필수 과정, 형상 관리

형상 관리는 CL 프로그래밍과는 직접적인 관련은 없지만, 소프트웨어를 개발하는 데 있어서 꼭 필요한 부분이기기 때문에 잠깐 언급하기로 한다.

KMP 시스템을 개발하는 데 있어서 사용하는 형상 관리 툴은 CVS(Control Version System)이다. CVS는 기본적으로 소스코드의 버전을 컨트롤하는데 사용되는 툴이다. 필자의 팀에서는 다음과 같은 것들을 CVS를 통해 관리한다.

[1] 직접 개발한 KMP 시스템 소스코드

[2] KMP 시스템을 개발하는 데 사용된, 직접 개발하지 않은 라이브러리들의 오픈소스 코드(TBNL, CLSQL 등)

[3] CL 개발 툴 소스 혹은 바이너리 패치 파일

[4] KMP 시스템 개발 환경에 대한 설정 파일 코드, 빌드 스크립트(.bashrc, .emacs, .lispworks, .sbclrc, SLIME 등)

[1]에 대한 관리는 대부분 하는 것이니 별도의 설명이 필요치 않을 것이다. [2]의 경우는 사용된 라이브러리에 대한 관리로 모든 개발자들이 동일한 버전을 사용하며, 업그레이드 직전 [1]의 코드와 문제가 없는지 확인한 후 CVS로 등록(import)한다. [3]은 주된 툴 자체에 대한 관리로 모든 개발자가 동일한 개발 툴을 사용토록 하기 위함이다. [4]는 모든 개발자들이 동일한 개발 환경을 유지하는 데 필요하다.

물론 이 글을 읽고 내용을 수행해 보기 위해서 앞과 유사한 형상 관리를 할 필요는 없다. 다만 양질의 소프트웨어를 개발하기 위해서는 그런 관리가 필요하다는 점, 그리고 이 글의 내용들이 하루아침에 만들어진 것이 아니라 꾸준한 노력을 통한 작은 변화들의 총합이라는 점을 밝히고 싶다.

CL 프로그래밍 기초

경험 있는 프로그래머라고 할지라도 처음 CL 개발 환경과 프로그래밍을 접하는 개발자는 어려움을 느낄 수 있기 때문에 SLIME 위주로 개발 환경을 사용하는 방법과 CL의 기본적인 개념 - 특히 CLHS를 읽는 방법 - 을 간단히 소개하기로 한다.

SLIME의 사용

Emacs를 실행한 후 Emacs에서 메타 키(보통의 경우 키)와 x를 같이 눌러서 커맨드 라인으로 이동해 “slime”을 입력하고(M-x slime) 엔터를 누르고 기다리면 SLIME이 실행되고 리스너 버퍼(*slime-repl[1]*)를 볼 수 있다.

; SLIME 2004-06-08

CL-USER>

리스너에서 숫자를 입력하고 엔터를 치면 CL 시스템이 결과를 읽고(read), 평가한 후(eval), 결과를 출력(print)하는 과정을 볼 수 있는데, 이 과정을 read-eval-print loop이라고 부른다.

CL-USER> (+ 1 2 3)

6

CL-USER>

테스트용 소스 파일을 만들어 보자. 을 누른 채로 x와 f를 순서대로 누르면(Ctrl-x-f) 커맨드 라인이 “Find file: ~/”로 바뀌면서 파일 이름을 입력할 것을 요구할 것이다. ‘test.lisp’을 입력하고 엔터를 치면 test.lisp 버퍼가 생긴다. CL 프로그래밍시 소스코드는 관례상 .cl, .lisp 등의 확장자를 붙인다. test.lisp 버퍼에서 다음과 같은 함수 정의를 시작으로 파일을 편집하자.

(defun hello-world ()

마지막 괄호를 입력한 후 를 누르면 커서가 다음 줄로 이동하면서 자동으로 들여쓰기가 된다. 그냥 엔터를 누른 경우에는 탭 키를 누름으로써 들여쓰기를 할 수 있다. 탭 키를 여러 번 치더라도 들여쓰기는 변화가 없음에 유의하자. Emacs는 리습의 문법을 알기 때문에 꼭 필요한 만큼의 들여쓰기 이외에는 허용하지 않으려고 할 것이다. 다음 줄에는 함수의 리턴 값을 쓰고 괄호를 닫는다.

Hello, welcome to world of Lisp!)

괄호를 닫으면 Emacs는 닫는 괄호가 어떤 여는 괄호와 짝을 이루는지 보여준다. 흔히 처음 Lisp를 접하는 사람이 겪는 어려움이 많은 괄호의 출현(?)이다. 익숙한 리습 프로그래머는 괄호에 신경을 거의 쓰지 않고, Emacs의 도움을 받아 괄호의 짝이 어떤 것인가 확인한다. 또 리습 코드를 읽을 때는 괄호보다는 들여쓰기에 주의를 기울이며 이해해야 한다. 예를 들어 앞의 함수 정의 밑에 다음과 같은 틀린 함수 정의를 입력해 보자.

(defun hello-world (

Hello, welcome to world of Lisp!

이제 두 번째 줄의 아무 위치에서나 탭 키를 쳐보면 들여쓰기가 이상하게 되는 것을 볼 수 있을 것이다. 익숙한 리습 프로그래머는 바로 앞에서 무엇인가 잘못되었음을 파악하고 수정할 것이다.

다시 첫 번째 함수 정의를 살펴보자. 만일 몇 개의 괄호를 제거한다면 코드가 어떻게 보일까?

defun hello-world ()

Hello, welcome to world of Lisp!

익숙한 Lisp 프로그래머는 괄호의 수에 상관없이 머릿속에서 위와 같은 형태로 기억하기도 한다. 이 코드의 의미는 “hello-world라는 함수는 정의하는데, 인자는 없고 하는 일은 (환영한다는) 문자열을 리턴한다”는 것이다. 예에서와 같이 Lisp에서는 함수 정의시 의미상으로 가장 마지막에 위치하는 것이 리턴된다. CL에서 함수는 항상 리턴 값을 갖는다.

제대로 정의된 hello-world를 복사해 리스너에서 평가하자. 함수 정의의 첫 부분인 괄호에 커서를 위치시킨 후 를 누르면 전체 정의가 선택된다. 를 눌러서 보이지 않는 임시 버퍼에 저장하고, 를 눌러서 “*sl”을 입력하고 탭을 누르면 가능한 버퍼들을 보여준다. “*slime-repl[1]*”을 완성하기 위해 한문자씩 입력하면서 탭을 눌러보면 어느 순간에 자동으로 확장될 것이다. 엔터를 눌러서 리스너 버퍼로 이동한다. 리스너 버퍼에서 를 누르면 함수 정의가 붙여진다. 이제 엔터를 누르면 리스너의 read-eval-print loop 과정을 거쳐서 다음과 같이 될 것이다.

CL-USER> (defun hello-world ()

Hello, welcome to world of Lisp!)

HELLO-WORLD

CL-USER>

리스너는 HELLO-WORLD를 리턴했다. defun에 커서를 옮긴 후 “C-c C-h”를 눌러보자. Emacs가 제대로 설정됐다면 웹 브라우저가 실행되고 CLHS의 defun 페이지를 보여줄 것이다. CLHS의 defun 페이지를 보면 “Syntax” 항목 마지막에 “=> function-name” 이라고 되어 있는데, 그 의미는 함수 정의 평가 결과로 함수 이름이 리턴된다는 것이다.

리스너에서 함수를 실행시켜 보자. CL의 함수 호출은 괄호로 시작해서 첫 번째가 연산자이고 나머지가 인자이다. hello-world의 경우에는 인자가 없으므로 다음과 같이 입력하고 엔터 키를 누르면 그 결과가 리턴된다.

CL-USER> (hello-world)

Hello, welcome to world of Lisp!

CL-USER>

다시 test.lisp 버퍼로 이동해서(Ctrl-x b) 두 번째 함수를 정의해 보자.

(defun happy-hacking ()

Happy Lisp hacking!)

이번에는 리스너로 이동하지 말고 파일 버퍼에서 바로 함수를 평가하고 실행시켜 보자. 커서를 함수 정의 시작과 끝 사이에 아무 곳에 위치시킨 후 Ctrl-M-x를 누르면 커맨드 라인에 “HAPPY-HACKING”이라고 함수 정의가 성공적으로 평가됐음을 알려주는 메시지를 볼 수 있다. 파일에 다음과 같이 함수를 호출하는 폼을 추가한 후 폼의 마지막으로 이동해(Ctrl-e) 그 폼을 평가하고(Ctrl-x-e), 커맨드 라인에 리턴 값이 출력됨을 확인하기 바란다.

(happy-hacking)

처음 CL을 접하는 대부분의 사람들이 원하는 것은 리턴 값에 만족하지 못하고 사이드 이펙트(side effect)로 무엇인가를 화면에 출력(프린트)하는 것이다. 이제 CL의 막강한 format 함수를 이용해 간단한 출력을 연습해 보자. test.lisp에 다음과 같은 함수 정의를 추가한다.

(defun hello-and-happy-hacking (name)

(format t ~&~A, ~A~&~A~& name (hello-world) (happy-hacking)))

앞의 함수 정의는 name이라는 인자를 하나 갖는다. 다음을 Ctrl-x-e를 이용해 평가해 보면 달랑 NIL만이 리턴되는 것을 볼 수 있다. 이것은 잘못된 것이 아니라 제대로 된 것인데, hello-and-happy-hacking은 표준 출력(*standard-output*)으로 출력하는 사이드 이펙트 함수이기 때문에 평가 결과 중 사이드 이펙트로 표준 출력에 출력하고 결과 - 이 경우 NIL - 를 리턴하기 때문이다.

(hello-and-happy-hacking 'lisper)

(hello-and-happy-hacking lisper)

앞의 내용을 복사해서 리스너에서 직접 입력하면 이번에는 내용이 리스너 버퍼의 표준 출력으로 출력된 후 각각 NIL이 리턴됨을 확인할 수 있다.

CL-USER> (hello-and-happy-hacking lisper)

lisper, Hello, welcome to world of Lisp!

Happy Lisp hacking!

NIL

CL-USER> (hello-and-happy-hacking 'lisper)

LISPER, Hello, welcome to world of Lisp!

Happy Lisp hacking!

NIL

CL-USER>

리스너에서 다음과 같이 입력해 보자.

(hello-and-happy-hacking lisper)

엔터를 치자마자 디버거로 이동할 것이다. SBCL의 경우 다음과 같은 결과를 얻었다.

The variable LISPER is unbound.

[Condition of type UNBOUND-VARIABLE]

Restarts:

0: [NIL] Try evaluating LISPER again.

1: [USE-VALUE] Specify a value to use this time instead of evaluating LISPER.

2: [SET] Specify a value to set LISPER to.

3: [ABORT] Abort handling SLIME request.

4: [ABORT] Quit process.

Backtrace:

0: CONDITIONS::CONDITIONS-ERROR (:INVISIBLEP T UNBOUND-VARIABLE (:NAME LISPER))

1: CONDITIONS::UNBOUND-SYMBOL-ERROR-INTERNAL (:INVISIBLEP T LISPER)

...

디버거는 첫째 줄에서 변수 LISPER가 unbound라고 불평하고 있다. 이유 있는 불평이다. 따옴표(‘)를 붙이면 입력한 그대로 심볼로 인정되지만, 따옴표가 없다면 변수로 취급하기 때문이다. 두 번째 줄에서는 예외 상황에 대한 설명으로 unbound-variable이라는 예외상황이 발생했음을 보여준다. Restarts에는 이 상황에서 가능한 선택이 나타나는데, 0을 누르면 변수를 다시 평가하라는 것이고, 1을 누르면 이번에만 다른 값을 사용하라는 것이며, 2를 누르면 변수를 특정 값으로 셋팅해 사용하라는 의미이다. 3과 4는 평가 요청을 취소하게 된다.

디버거의 메시지를 잘 읽으면서 각 선택을 한번씩 시도해 보기 바란다. Backtrace는 문제 발생까지의 스택을 보여준다. 디버거에서 가능한 다양한 명령들은 을 통해 확인하고 익힐 수 있다.

SLIME의 단축키는 리스너 버퍼나 리습 파일 버퍼에서 을 통해 볼 수 있으므로, 하루에 다섯 개 정도의 명령어를 골라서 익히면 한달 정도 지나면 잘 사용할 수 있으리라고 생각한다.

CL 기초

CL 프로그래밍을 시작하기 위해 필요한 기본 개념과 CL의 구성요소들에 대해서 알아보기로 하자. 간단하게 나열된 예들은 리스너에서 실행해 봐도 좋을 것이다.

Lisp은 다른 여러 가지 자료구조를 갖고 있지만, 역시 리스트가 가장 기본적인 자료구조이다. Lisp에서는 괄호로 시작하고 끝나는 구성요소를 익스프레션(expression, 수식) 혹은 폼(form)이라고 부르며, 그 외의 모든 것들을 아톰(atom)이라고 부른다. 아톰은 있는 그대로 평가되며, 그 예로는 문자열, 배열, 숫자, CLOS 객체 등 다른 프로그래밍 언어에서도 흔히 볼 수 있는 것들 뿐 아니라 심볼(symbol), 함수 등 다소 생소한 것들이 모두 포함된다.

“문자열”

#(1 2 3) ; 배열

3.14 ; 숫자

'cl-symbol ; 심볼. 쿼트(따옴표)로 시작하는 몇몇 특수 문자들을 제외한 문자들의 모임

#'+ ; 함수. 함수를 지칭하기 위해서는 “#'” 가 사용된다.

폼은 이미 언급한 바와 같이 괄호로 감싼 형태이다. 따옴표로 시작하는 폼은 보이는 그대로 데이터로 취급하며, 그렇지 않은 것들은 코드로 취급한다.

'(cons a b) ; 세 개의 아톰 - cons, a, b 로 이루어진 리스트

(cons 'a 'b) ; 함수 cons를 각각 심볼인 두 개의 인자로 호출하는 코드. 결과는 (a . b)

데이터가 아닌 코드 폼에서 폼의 첫 번째 오는 것에 따라 더 자세히 폼을 구별할 수 있다. 즉 코드 폼의 첫 번째 요소는 스페셜 오퍼레이터, 매크로, 함수 중 하나가 된다. 스페셜 오퍼레이터 혹은 매크로로 시작되는 폼을 “스페셜 폼(special form)”이라고 부르며 보통 함수로 시작되는 폼은 그냥 “폼”이라고 부르는 것이 관례이다.

스페셜 오퍼레이터는 CL의 기초 구성요소로 사용자인 CL 프로그래머들은 이런 종류의 구성요소를 만들 수 없다. 매크로와 함수는 이런 기초 구성요소 및 다른 매크로와 함수를 이용해 만들어진 것으로, CL 시스템에 이미 존재하는 것들도 많지만 CL 프로그래머들도 필요에 따라 정의해 CL 시스템을 확장해 나갈 수 있다. 비교적 많이 사용하게 될 스페셜 오퍼레이터로는 if, let, let*, flet, labels, progn, setq, unwind-protect, quote, return-from 등이 있으며, 매크로는 defun, cond, loop, do, dolist, defclass, defstruct, defmethod 등이 있다. 함수는 양적으로 많기 때문에 몇 가지 꼽아서 나열한다는 것이 무의미하다.

다음은 CL의 매크로를 정의하는 예이다. 이 예를 통해 스페셜 오퍼레이터, 매크로, 함수 등이 어떻게 쓰였고 의미가 어떻게 다른지 살펴보기로 하자.

(defmacro bind-when ((var test-form) &body form)

`(let ((,var ,test-form))

(when ,var

,@form)))

앞의 폼은 defmacro 폼으로 defmacro는 매크로이고, bind-when은 정의하는 매크로 이름, var, test-form, form등은 매크로 인자이다. 이 매크로는 런타임에 test-form을 평가한 후 그것이 NIL이 아닌 경우에만 form 부분을 수행한다. 앞의 스페셜 폼에서 let은 스페셜 오퍼레이터이고, defmacro, when 등은 매크로이다(when은 if 스페셜 오퍼레이터를 이용해 만든 매크로이다). 앞에서 정의한 매크로는 다음과 같이 사용될 것이다.

;;; bind-when은 매크로. 매크로를 사용하는 스페셜 폼

(dolist (maybe-integer-string '(abc 123 def 456))

(bind-when (integer (parse-integer maybe-integer-string :junk-allowed t))

(format t ~&~A + 1 = ~A integer (1+ integer))))

앞의 폼의 의미는 문자열들의 리스트로부터 차례로 문자열을 maybe-integer-string에 바인드해 루프를 돌면서 parse-integer를 하는데, 그 결과가 있을 때만 1을 더해 결과를 프린트하라는 것이다. 매크로 dolist로 시작하는 스페셜 폼은 bind-when 매크로, 함수 parse-integer와 format을 사용한다.

이상과 같이 살펴본 스페셜 오퍼레이터, 매크로, 함수는 CL의 중요한 구성요소들로 다음과 같은 차이가 있다.

[1] 스페셜 오퍼레이터로 시작하는 폼은 미리 정해진 규칙에 따라 평가된다. 프로그래머는 새로운 스페셜 오퍼레이터를 정의할 수 없다.

[2] 매크로로 시작하는 폼 역시 미리 정해진 규칙에 따라 평가된다. 프로그래머는 새로운 매크로를 정의할 수 있으며, 정의된 매크로는 이미 시스템에 존재하는 매크로와 차별이 없다.

[3] 함수로 시작하는 폼은 함수 호출로 인자들이 모두 평가된 후에 함수 호출이 일어난다. 프로그래머는 새로운 함수를 정의할 수 있으며, 정의된 함수는 시스템에 이미 존재하는 함수와 차별이 없다.

[1]과 [2]는 거의 동일하므로, [2]와 [3]을 CLHS에서 비교해 보기로 한다. 앞의 예에서 매크로 when과 함수 1+를 중심으로 살펴보자. CLHS에 정의된 매크로 when과 함수 1+, 각각의 문법을 보면 다음과 같다.

Macro WHEN

Syntax:

when test-form form* => result*

Function 1+

Syntax:

1+ number => successor

웬만한 개발자라면 앞의 문법 정의가 어떤 의미를 갖는지 짐작할 수 있을 것이다. 매크로 when은 하나의 test-form과 0개 이상의 form을 받아들여 0개 이상의 결과를 리턴한다. 함수 1+의 경우에는 하나의 number를 받아서 하나의 successor를 리턴한다. 앞의 문법 정의 부분에서는 평가 순서에 대해서는 나와 있지 않다. 함수의 경우는 항상 인자들이 평가된 후 실제 함수가 호출되기 때문에 평가 순서와 규칙을 함수마다 설명을 달 필요가 없다. 예를 들면, (1+ (1+ (1+ 3)))은 가장 내부의 1+ 폼을 시작해서 바깥쪽 1+폼을 차례로 호출해 평가한다. 하지만 when과 같은 매크로는 특별한 규칙에 따라 평가해야 한다.

(when (not (zerop number-of-classes))

(/ sum-of-marks number-of-classes))

앞의 예에서 만일 함수처럼 내부에서부터 폼이 평가된다면, zerop 함수 호출로 number-of-classes의 수를 검사하기 전에 이미 “/” 함수가 호출되어 number-of-classes가 0인 경우 에러가 발생할 것이다. 그렇게 때문에 모든 스페셜 오퍼레이터와 매크로의 경우에는 CLHS에 폼의 평가 순서에 대한 규칙이 있으며, when의 경우 다음과 같이 규칙이 명시되어 있다(실제 CLHS에서 불필요한 부분을 편집했기 때문에 내용이 실제와는 약간 다르다).

when allows the execution of forms to be dependent on a single test-form.

In a when form if the test-form yields true, the forms are evaluated in order from left to right and the values returned by the forms are returned from the when form. Otherwise, if the test-form yields false, the forms are not evaluated, and the when form returns nil.

CL 개발자는 CLHS를 읽는 법에 대해 익숙해야 하며, 참조할 때는 특히 스페셜 오퍼레이터, 매크로, 함수 등의 여부에 따라 특별한 규칙 부분에 유념해 내용을 파악해야 한다.

이런 스페셜 오퍼레이터나 매크로와 같이 프로그래머가 기억해야 할 요소들이 많은 프로그래밍 언어들은 익숙해지기가 쉽지 않다. CL은 다행히도 앞에서 나열한 18개의 스페셜 오퍼레이터와 매크로 정도면 충분히 프로그래밍을 시작할 수 있다. 물론 프로그래머가 자신의 목적을 위해 스스로 정의하는 표준 이외의 매크로는 스스로 기억하기 쉬울 것이다.

CL로 개발하는 프로그래머는 매크로를 작성하기 위해서는 이미 CL 표준에 존재하는 매크로들이 어떤 식으로 규칙을 갖는가를 잘 파악한 후 그들과 유사한 형태로 매크로를 정의하는 것이 좋다. 예를 들면, CL에서 흔히 매크로를 사용하는 예 중 하나가 컨텍스트를 설정하는 것인데, 이런 매크로의 경우 “with- 등으로 시작하며, 컨텍스트에서 사용할 변수들을 바인딩하게 된다.

(with-parameter-binding-to-persistent-object (user-name user-id address)

;; bind 된 user-name, user-id, address 등을 사용한다.

)

이상의 내용은 CL 프로그래밍 책에서 흔히 나타나지 않지만, 중요한 내용이므로 지면을 통해 강조했다. 나머지는 독자 여러분의 몫으로 CL 프로그래밍 입문서 등을 약 한달 동안 참고하면서 스스로 CL에 익숙해지도록 노력하기 바란다.

새로운 공간에서 정보 공유를

CL 프로그래밍에 대해 많은 이야기를 하지 못한 것 같아서 아쉬움이 남지만, 제대로 CL 프로그래밍을 하기 위한 준비를 하는 것이 더 중요하다고 생각했다. 첫 회를 통해 많은 독자들이 남은 2, 3회의 연재를 제대로 소화할 수 있는 기회를 갖기를 고대한다. 다시 한번 강조하지만 스스로 적극적으로 배우고 익히지 않으면 결코 수준 높은 개발자가 되기 힘들다. 이제는 인터넷의 발달로 원한다면 얼마든지 필요한 정보를 구할 수 있는 시대가 되었기 때문에 독자 스스로 얼마나 노력하는가 여부에 따라 미래에 큰 차이를 만들어 낼 수 있다.

마지막으로 CL에 대한 질문이나 의견은 다른 사람들과 함께 공유할 수 있도록 한국리습사용자모임을 이용해 주기 바란다. 특히 앞으로 한달에 한번 정도 직접 만나서 서로 의견이나 팁 등의 정보를 나누거나 세미나 등을 통해 서로 경험을 주고받을 수 있는 정기 모임을 준비하고 있으므로 관심 있는 독자들의 많은 참여를 기대한다. 이 글 자체에 대한 내용은 한국리습사용자모임에 있는 ‘최종원’으로 검색하면 발견할 수 있는 필자의 페이지를 이용해 줄 것을 당부한다. @

* 이 기사는 ZDNet Korea의 자매지인 마이크로소프트웨어에 게재된 내용입니다.