Lisp을 좋아하는 사람들의 그룹(http://groups.google.com/group/lisp-korea)에서 Common Lisp을 하면서 Common Lisp을 이용한 라이브러리들에 대하여 스터디하고 있습니다. 현재까지 C프로그램과의 링킹부터 Socket 프로그래밍 및 Database 프로그래밍까지 진행하였습니다.
마지막으로 제가 웹 프로그래밍에 대한 내용을 발표하기로 하였습니다. ^^~
사실 이번에 웹 프로그래밍 발표를 위하여 여러가지 자료를 찾으면서 역시 Common Lisp은 긴 역사만큼 강력하다는 것을 느꼈습니다.
실제 활용을 하면서 Common Lisp을 만지다보니 그동안 잘 이해되지 않았던 부분도 자연스럽게 많이 이해되었습니다. ^^
Hunchentoot도 정말 쓸만하구나 하는 생각이 들었습니다.
참고로 http://www.adampetersen.se/articles/lispweb.htm 의 내용을 바탕으로 발표 자료를 준비하였습니다.
아울러 Ubuntu 9.10 + SBCL + Slime(2009-06-15) + Emace 22.2.1 환경 하에서 작성하였습니다.
사실 처음에 Windows 7 + Cygwin + SBCL + Slime으로 하다가 ASDF 관련한 GPG 에러가 있어서 Ubuntu로 갈아탔습니다. 이거 때문에 고생점 했었습니다.
1. Hunchentoot에 대하여
Hunchentoot는 Common Lisp으로 작성된 웹 서버입이며, 다이나믹한 웹사이트를 구성할 수 있도록 도와주는 툴킷입니다. 단독실행(Stand-alone)될 수 있으며, HTTP/1.1 스펙을 지원하고, DBMS와 연결 및 SSL 등을 지원합니다. 아울러 Hunchentoot는 Session을 처리할 수 있으며, Cookie 기반의 Session 처리와 Cookie를 사용하지 않는 Session 처리도 가능합니다. 또한 Logging을 하거나 사용자 정의 에러 처리를 할 수 있습니다.
1.1 Hunchentoot 설치
Hunchentoot는 당근 ASDF를 기반으로 쉽게 설치가 가능합니다. ASDF에 대한 자세한 사항은 나중에 한번 정리해서 올리겠습니다.
설치 절차는 다음과 같습니다.
참고로 저의 경우 /usr/share/common-lisp/ 에 Common Lisp 관련된 자료를 몰아넣는 것을 좋아합니다. Slime도 여기에 설치했습니다. 아울러 ASDF-INSTALL 시 System-wide install을 하여 다른 계정으로도 쉽게 접근할 수 있도록 구성하는 것을 좋아합니다. Hunchentoot를 설치하다보면 계속 GPG 관련한 확인을 해주어야 합니다. 간단하게 ASDF-INSTALL 전에 다음과 같이 설정하시면 GPG 관련 항목을 더 이상 체크하지 않습니다.
1.2 Hunchentoot 실행
ASDF를 이용하여 Hunchentoot가 정상적으로 설치했다면 다음과 같이 실행하면 됩니다. 8080포트를 사용합니다.
사실 처음 CL-WHO를 들었을때 WHO(국제보건기구)에서 만들었나~ 하는 생각을 하였습니다. ㅎㅎㅎ CL은 당은 Common Lisp의 약자구요~ WHO는 With-Html-Output의 약자라는군요~ 참 했갈리게 하면서도 확실히 기억되는 이름입니다.
CL-WHO는 Lisp의 마크업 언어(Markup Language)입니다. Lisp의 특징인 S-expression을 기반으로 HTML, XHTML, XML 등의 마크업을 작성할 수 있도록 도와주는 넘입니다. 참고로 이와 비슷한 넘이 HTML-TEMPLATE란 넘이 있습니다.
아래의 Lisp 코드에서 (:XXX) 가 바로 CL-WHO 의 예 입니다.
2.1 CL-WHO 설치
당근 ASDF 를 이용하여 다음과 같이 설치합니다.
3. Parenscript 에 대하여
이름과 같이 Common Lisp 하에서 JavaScript를 CL-WHO와 같이 S-expression으로 표현할 수 있도록 도와주는 넘이 Parenscript 입니다. 이번 예제에서는 많이 쓰지 않았습니다. 하지만 재미 있는 기능이 많은 것 같습니다. :-)
이렇게 사용된다고 하네요~
4. K-Lisper 들을 위한 Lisp 책 투표 사이트 구축하기
자 이제 Common Lisp에서 웹 프로그래밍을 하기 위한 환경들을 살펴보았으니 본격적으로 웹 프로그래밍을 시작해보겠습니다.
예제로 만들어볼 웹 사이트를 간단하게 설명드리면, K-Lisper들이 자신이 선호하는 책에 투표를 하면 투표 결과가 바로 나오며, 만약 선호하는 책이 없으면 등록할 수 있는 웹 사이트입니다.
구조는 간단하지만, 간단한 투표 시스템을 만들려고 해도 사실 어려운 것이 사실입니다. 하지만 Common Lisp을 사용하였을 때 얼마나 효과적으로 웹 프로그래밍이 가능한지 한번 확인해보시기 바랍니다. :-)
4.1 패키지 설정
K-Lisper 책 투표 사이트를 위한 패키지를 새로 만듭니다.
자~ 만든 패키지로 이동합시다.
4.2 책(Book) 클래스 정의 및 로직 구성
Lisp에도 클래스가 있어라고 이야기하시는 분이 있다면, 김영태님이 발표하신 CLOS에 관한 내용을 확인해보시기 바랍니다. 제 블로그에 Lisp의 객체지향은 그 무엇보다 강력했다. 란 제목으로도 올려놨습니다.
우선 책 투표 사이트이므로 책(Book) 클래스를 만들겠습니다.
상당히 간단한 책 클래스입니다. 필수적으로 입력받아야 하는 책 명(name)과 얼마나 투표했는지 나타내는 votes가 존재합니다.
그럼 새로운 책을 하나 만들어 볼까요~ :-)
예제 삼아 Lisp을 좋아하는 사람들의 그룹(http://groups.google.com/group/lisp-korea)에서 처음 공부하였던 책 제목을 넣어 봤습니다.
생성된 Common Lisp 책 객체가 정상적으로 동작하는지 확인하기 위하여 책 명을 확인해보겠습니다.
잘 들어가 있군요~ :-) 투표 건수도 확인해보겠습니다.
네~ 아직 투표는 하지 않았으니 0건이 맞습니다. 한번 투표해보겠습니다.
incf 는 1씩 증가시켜주는 function 입니다. 제 Emacs는 WARNING되는 내용을 확인할 수 있도록 설정되어 있어 약간씩 내용이 틀릴 수 있습니다. 참고하세요~
책(Book) 클래스를 더 확장하겠습니다.
사용자가 선택한 책을 찾아서 투표해주는 메소드를 만들겠습니다.
자~ 새로운 메소드를 통하여 한번 투표를 해보겠습니다.
책들을 담을 전역 변수를 만들겠습니다. 앞으로 이곳에 책들이 저장될 것입니다.
자~ 전역 변수를 기준으로 책 명으로 책을 찾아주는 메소드를 만들어 보겠습니다.
그리고 책이 존재하는지 여부를 확인하는 function을 만들어 보겠습니다.
아울러 책을 투표된 건수를 바탕으로 정렬해서 출력하는 function도 만들겠습니다. 나중에 화면에 출력할때 투표건에 따라 정렬할 때 사용할 예정입니다.
마지막으로 책을 추가하는 function을 만들어 보겠습니다.
자~ 새로 만든 function들이 정상적으로 동작하는지 확인해보겠습니다.
이로서 책 투표 사이트 구축을 위한 핵심 로직 구성이 끝났습니다. 이제 본격적으로 웹 페이지를 구성해 봅시다~
4.3 웹 사이트 구성
CL-WHO를 본격적으로 이용해볼 시간이 왔습니다. 간단하게 한번 CL-WHO를 테스트 해봅시다.
표준적으로 사용할 HTML 페이지의 구조를 잡는 Macro를 작성하려고 합니다. 즉, XHTML의 공통적인 요소를 담고 있는 Macro 입니다. 이 Macro를 통하여 불필요한 요소를 없애고 변화되는 내용만 반영하게 만들것입니다.
주의하셔야 할 점은 ,title 와 ,@body 의 , 는 마침표(.)가 아니라 쉼표(,)라는 것입니다.
자~ 첫 페이지인 index.htm을 한번 만들어 봅시다. Macro로 이미 정의한 standard-page의 위력이 발휘되는 순간입니다.
이제 만든 페이지를 index.htm으로 걸어 봅시다.
웹 브라우져에서 http://localhost:8080/index.htm 이 정상적으로 출력되는지 한번 확인해보세요~
이렇게 등록하는 절차를 더욱 쉽게 하도록 Macro를 사용하겠습니다.
그리고 다시 index 페이지를 작성해보겠습니다.
웹 브라우져에서 다시 http://localhost:8080/index.htm 을 입력하고 정상적으로 변경되었는지 확인해보세요~ 두번에 해야할 절차를 한번에 간단하게 끝냈습니다. 와우~ 매크로여~ :-)
본격적으로 index.htm 페이지에 투표를 할 수 있는 기능을 추가해보겠습니다.
길어 보이긴 합니다만, 기종의 HTML 작업의 경우 JSP나 PHP 또는 ASP 파일을 각각 만들고 각각 서버에 올려서 다시 컴파일 되는 과정이나 인터프리팅 되는 과정을 거쳐서 결과가 나오지만, Common Lisp의 웹 프로그래밍은 전혀 파일이 필요없습니다.
따라서 언제라도 index.htm 을 바로 변경할 수 있으며 만약 미리 만든 lisp 파일을 load한다고 하여도 파일속의 내용이 현저하게 적습니다. 웹 브라우져로 HTML 소스를 확인해보신다면 아마 팍팍 느끼실 것입니다.
이제 투표 결과를 받는 페이지입니다.
index.htm에서 Vote! 를 클릭하면 vote.htm으로 왔다가 바로 redirect되어 /index.htm으로 돌아가게 되어있습니다. 여기서 주목할 점은 이전에 만들었던 function인 vote-for를 로직으로 바로 활용했다는 점입니다. 그리고 "name" parameter 역시 간단하게 바로 받아서 처리합니다.
얼마나 멋집니까~ 하하하~ ;-)
이제~ 새로운 책을 추가하는 페이지를 만들겠습니다. 투표할 목록에 원하는 책이 없는 경우 간단하게 책 명을 입력하는 페이지입니다.
이 페이지에서 드디어 Parenscript 를 사용합니다. 중간에 ps-inline 부터 사용하며, 만약 내용이 없는 경우 다시 입력을 받을 수 있도록 JavaScript로 입력값 검증을 처리하는 부분입니다.
자 새로운 책명을 입력받았다면, 책을 추가하는 페이지입니다. JavaScript에서 이미 걸렸겠지만, 다시 한번 빈 내용이 들어오는지 확인합니다. 이런 처리까지 해주어야 정말 좋은 웹 프로그래밍이라고 생각합니다. 참고로 로직 상으로도 이미 book-stored? function을 통하여 중복 방지 처리가 되어있습니다.
자~ 이제 index.htm에 있는 here 를 눌러서 새로운 책을 한번 추가해보세요~ :-)
여기까지가 Common Lisp의 새로운 웹 프로그래밍의 세계입니다. 다른 언어도 많은 장점을 제공하지만, Common Lisp이 당근 훌륭한 기능을 제공한다고 생각되네요~
아마 웹 브라우져의 내용이 궁금하신 분들이 많으시리라 생각됩니다. 한번씩 해보세요~ 따라하시기 편하도록 구성하였습니다. ;-)
마지막으로 제가 웹 프로그래밍에 대한 내용을 발표하기로 하였습니다. ^^~
사실 이번에 웹 프로그래밍 발표를 위하여 여러가지 자료를 찾으면서 역시 Common Lisp은 긴 역사만큼 강력하다는 것을 느꼈습니다.
실제 활용을 하면서 Common Lisp을 만지다보니 그동안 잘 이해되지 않았던 부분도 자연스럽게 많이 이해되었습니다. ^^
Hunchentoot도 정말 쓸만하구나 하는 생각이 들었습니다.
참고로 http://www.adampetersen.se/articles/lispweb.htm 의 내용을 바탕으로 발표 자료를 준비하였습니다.
아울러 Ubuntu 9.10 + SBCL + Slime(2009-06-15) + Emace 22.2.1 환경 하에서 작성하였습니다.
사실 처음에 Windows 7 + Cygwin + SBCL + Slime으로 하다가 ASDF 관련한 GPG 에러가 있어서 Ubuntu로 갈아탔습니다. 이거 때문에 고생점 했었습니다.
1. Hunchentoot에 대하여
Hunchentoot는 Common Lisp으로 작성된 웹 서버입이며, 다이나믹한 웹사이트를 구성할 수 있도록 도와주는 툴킷입니다. 단독실행(Stand-alone)될 수 있으며, HTTP/1.1 스펙을 지원하고, DBMS와 연결 및 SSL 등을 지원합니다. 아울러 Hunchentoot는 Session을 처리할 수 있으며, Cookie 기반의 Session 처리와 Cookie를 사용하지 않는 Session 처리도 가능합니다. 또한 Logging을 하거나 사용자 정의 에러 처리를 할 수 있습니다.
1.1 Hunchentoot 설치
Hunchentoot는 당근 ASDF를 기반으로 쉽게 설치가 가능합니다. ASDF에 대한 자세한 사항은 나중에 한번 정리해서 올리겠습니다.
설치 절차는 다음과 같습니다.
CL-USER> (require 'asdf)
NIL
CL-USER> (require 'asdf-install)
("ASDF-INSTALL")
CL-USER> (asdf-install:install :hunchentoot)
Install where?
1) System-wide install:
System in /usr/lib/sbcl/site-systems/
Files in /usr/lib/sbcl/site/
2) Personal installation:
System in /root/.sbcl/systems/
Files in /root/.sbcl/site/
--> 1
Downloading 140293 bytes from http://weitz.de/files/hunchentoot.tar.gz ...
Installing /root/HUNCHENTOOT.asdf-install-tmp in /usr/lib/sbcl/site/,/usr/lib/sbcl/site-systems/
hunchentoot-1.1.0/
hunchentoot-1.1.0/acceptor.lisp
hunchentoot-1.1.0/CHANGELOG
hunchentoot-1.1.0/CHANGELOG_TBNL
hunchentoot-1.1.0/compat.lisp
hunchentoot-1.1.0/conditions.lisp
hunchentoot-1.1.0/cookie.lisp
.....
참고로 저의 경우 /usr/share/common-lisp/ 에 Common Lisp 관련된 자료를 몰아넣는 것을 좋아합니다. Slime도 여기에 설치했습니다. 아울러 ASDF-INSTALL 시 System-wide install을 하여 다른 계정으로도 쉽게 접근할 수 있도록 구성하는 것을 좋아합니다. Hunchentoot를 설치하다보면 계속 GPG 관련한 확인을 해주어야 합니다. 간단하게 ASDF-INSTALL 전에 다음과 같이 설정하시면 GPG 관련 항목을 더 이상 체크하지 않습니다.
CL-USER> (setq asdf-install::*verify-gpg-signatures* nil)
1.2 Hunchentoot 실행
ASDF를 이용하여 Hunchentoot가 정상적으로 설치했다면 다음과 같이 실행하면 됩니다. 8080포트를 사용합니다.
KLISPER> (hunchentoot:start (make-instance 'hunchentoot:acceptor :port 8080))간단하게 실행됩니다.
#<ACCEPTOR (host *, port 8080)>
1.3 Hunchentoot 종료
간단하게 종료됩니다. 사실 종료는 아직~ 해보지 않았습니다. :-)
KLISPER> (hunchentoot:stop *hunchentoot-server*)
2. CL-WHO에 대하여
사실 처음 CL-WHO를 들었을때 WHO(국제보건기구)에서 만들었나~ 하는 생각을 하였습니다. ㅎㅎㅎ CL은 당은 Common Lisp의 약자구요~ WHO는 With-Html-Output의 약자라는군요~ 참 했갈리게 하면서도 확실히 기억되는 이름입니다.
CL-WHO는 Lisp의 마크업 언어(Markup Language)입니다. Lisp의 특징인 S-expression을 기반으로 HTML, XHTML, XML 등의 마크업을 작성할 수 있도록 도와주는 넘입니다. 참고로 이와 비슷한 넘이 HTML-TEMPLATE란 넘이 있습니다.
아래의 Lisp 코드에서 (:XXX) 가 바로 CL-WHO 의 예 입니다.
KLISPER> (defmacro standard-page ((&key title) &body body)
`(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
(:head
(:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
(:title ,title)
(:link :type "text/css" :rel "stylesheet" :href "/retro.css"))
(:body
(:div :id "header" ; Start all pages with our header.
(:img :src "/logo.jpg" :alt "Commodore 64" :class "logo")
(:span :class "strapline" "Vote on your favourite Retro Game"))
,@body))))
2.1 CL-WHO 설치
당근 ASDF 를 이용하여 다음과 같이 설치합니다.
CL-USER> (asdf-install:install :cl-who)
Install where?
1) System-wide install:
System in /usr/lib/sbcl/site-systems/
Files in /usr/lib/sbcl/site/
2) Personal installation:
System in /root/.sbcl/systems/
Files in /root/.sbcl/site/
--> 1
Downloading 19817 bytes from http://weitz.de/files/cl-who.tar.gz ...
Installing /root/CL-WHO.asdf-install-tmp in /usr/lib/sbcl/site/,/usr/lib/sbcl/site-systems/
cl-who-0.11.1/
cl-who-0.11.1/CHANGELOG
cl-who-0.11.1/cl-who.asd
.....
3. Parenscript 에 대하여
이름과 같이 Common Lisp 하에서 JavaScript를 CL-WHO와 같이 S-expression으로 표현할 수 있도록 도와주는 넘이 Parenscript 입니다. 이번 예제에서는 많이 쓰지 않았습니다. 하지만 재미 있는 기능이 많은 것 같습니다. :-)
이렇게 사용된다고 하네요~
(lambda (x)
(case x
(1 (loop repeat 3 do (alert "foo")))
(:bar (alert "bar"))
(otherwise 4)))
상기 Lisp 코드가 변환되면 이런 JavaScript 코드가 나온다고 합니다.
function (x) {
switch (x) {
case 1:
for (var _js1 = 0; _js1 < 3; _js1 += 1) {
alert('foo');
};
return null;
case 'bar':
return alert('bar');
default:
return 4;
};
};
4. K-Lisper 들을 위한 Lisp 책 투표 사이트 구축하기
자 이제 Common Lisp에서 웹 프로그래밍을 하기 위한 환경들을 살펴보았으니 본격적으로 웹 프로그래밍을 시작해보겠습니다.
예제로 만들어볼 웹 사이트를 간단하게 설명드리면, K-Lisper들이 자신이 선호하는 책에 투표를 하면 투표 결과가 바로 나오며, 만약 선호하는 책이 없으면 등록할 수 있는 웹 사이트입니다.
구조는 간단하지만, 간단한 투표 시스템을 만들려고 해도 사실 어려운 것이 사실입니다. 하지만 Common Lisp을 사용하였을 때 얼마나 효과적으로 웹 프로그래밍이 가능한지 한번 확인해보시기 바랍니다. :-)
4.1 패키지 설정
K-Lisper 책 투표 사이트를 위한 패키지를 새로 만듭니다.
CL-USER> (defpackage :klisper (:use :cl :cl-who :hunchentoot :parenscript))
#<PACKAGE "KLISPER">
자~ 만든 패키지로 이동합시다.
CL-USER> (in-package :klisper)
#<PACKAGE "KLISPER">
4.2 책(Book) 클래스 정의 및 로직 구성
Lisp에도 클래스가 있어라고 이야기하시는 분이 있다면, 김영태님이 발표하신 CLOS에 관한 내용을 확인해보시기 바랍니다. 제 블로그에 Lisp의 객체지향은 그 무엇보다 강력했다. 란 제목으로도 올려놨습니다.
우선 책 투표 사이트이므로 책(Book) 클래스를 만들겠습니다.
KLISPER> (defclass book()
((name :initarg :name)
(votes :initform 0)
)
)
#<STANDARD-CLASS BOOK>
상당히 간단한 책 클래스입니다. 필수적으로 입력받아야 하는 책 명(name)과 얼마나 투표했는지 나타내는 votes가 존재합니다.
그럼 새로운 책을 하나 만들어 볼까요~ :-)
예제 삼아 Lisp을 좋아하는 사람들의 그룹(http://groups.google.com/group/lisp-korea)에서 처음 공부하였던 책 제목을 넣어 봤습니다.
KLISPER> (setf klispers-books (make-instance 'book :name "Common Lisp: A Gentle Introduction to Symbolic Computation"))
; in: LAMBDA NIL
; (SETF KLISPER::KLISPERS-BOOKS
; (MAKE-INSTANCE 'KLISPER::BOOK :NAME
; "Common Lisp: A Gentle Introduction to Symbolic Computation"))
; ==>
; (SETQ KLISPER::KLISPERS-BOOKS
; (MAKE-INSTANCE 'KLISPER::BOOK :NAME
; "Common Lisp: A Gentle Introduction to Symbolic Computation"))
;
; caught WARNING:
; undefined variable: KLISPERS-BOOKS
;
; compilation unit finished
; Undefined variable:
; KLISPERS-BOOKS
; caught 1 WARNING condition
#<BOOK {D321AE9}>
생성된 Common Lisp 책 객체가 정상적으로 동작하는지 확인하기 위하여 책 명을 확인해보겠습니다.
KLISPER> (name klispers-books)
"Common Lisp: A Gentle Introduction to Symbolic Computation"
잘 들어가 있군요~ :-) 투표 건수도 확인해보겠습니다.
KLISPER> (votes klispers-books)
0
네~ 아직 투표는 하지 않았으니 0건이 맞습니다. 한번 투표해보겠습니다.
KLISPER> (incf (votes klispers-books))
; (LET* ((#:TMP1390 KLISPER::KLISPERS-BOOKS)
; (#:G1391 1)
; (#:NEW1389 (+ (KLISPER::VOTES #:TMP1390) #:G1391)))
; (FUNCALL #'(SETF KLISPER::VOTES) #:NEW1389 #:TMP1390))
;
; caught WARNING:
; undefined variable: KLISPERS-BOOKS
;
; compilation unit finished
; Undefined variable:
; KLISPERS-BOOKS
; caught 1 WARNING condition
1
KLISPER> (votes klispers-books)
1
incf 는 1씩 증가시켜주는 function 입니다. 제 Emacs는 WARNING되는 내용을 확인할 수 있도록 설정되어 있어 약간씩 내용이 틀릴 수 있습니다. 참고하세요~
책(Book) 클래스를 더 확장하겠습니다.
KLISPER> (defclass book()
((name :reader name
:initarg :name)
(votes :accessor votes
:initform 0)
)
)
#<STANDARD-CLASS BOOK>
사용자가 선택한 책을 찾아서 투표해주는 메소드를 만들겠습니다.
KLISPER> (defmethod vote-for (user-selected-book)
(incf (votes user-selected-book))
)
STYLE-WARNING: Implicitly creating new generic function VOTE-FOR.
#<STANDARD-METHOD VOTE-FOR (T) {C847BF1}>
자~ 새로운 메소드를 통하여 한번 투표를 해보겠습니다.
KLISPER> (votes klispers-books) ; 현재 1건 투표됨
1
KLISPER> (vote-for klispers-books) ; 투표함
2
KLISPER> (votes klispers-books) ; 2건 투표됨.
2
책들을 담을 전역 변수를 만들겠습니다. 앞으로 이곳에 책들이 저장될 것입니다.
KLISPER> (defvar *books* '())
*BOOKS*
자~ 전역 변수를 기준으로 책 명으로 책을 찾아주는 메소드를 만들어 보겠습니다.
KLISPER> (defun book-from-name(name)
(find name *books* :test #'string-equal :key #'name))
STYLE-WARNING: redefining BOOK-FROM-NAME in DEFUN
BOOK-FROM-NAME
그리고 책이 존재하는지 여부를 확인하는 function을 만들어 보겠습니다.
KLISPER> (defun book-stored? (book-name)
(book-from-name book-name)
)
BOOK-STORED?
아울러 책을 투표된 건수를 바탕으로 정렬해서 출력하는 function도 만들겠습니다. 나중에 화면에 출력할때 투표건에 따라 정렬할 때 사용할 예정입니다.
KLISPER> (defun books()
(sort (copy-list *books*) #'> :key #'votes)
)
BOOKS
마지막으로 책을 추가하는 function을 만들어 보겠습니다.
KLISPER> (defun add-book(name)
(unless (book-stored? name)
(push (make-instance 'book :name name)
*books*)
)
)
ADD-BOOK
자~ 새로 만든 function들이 정상적으로 동작하는지 확인해보겠습니다.
KLISPER> (books) ; 현재 한권의 책도 없음.
NIL
KLISPER> (add-book "Common Lisp") ; 새로운 책 추가함.
(#<BOOK {C656311}>)
KLISPER> (book-from-name "Common Lisp") ; 책명으로 책을 찾음
#<BOOK {C656311}>
KLISPER> (add-book "Common Lisp") ; 같은 명칭의 책을 추가하였지만, 추가되지 않음. 즉 기능이 정상임.
NIL
KLISPER> (mapcar #'name (books)) ; 책 목록을 출력함.
("Common Lisp")
이로서 책 투표 사이트 구축을 위한 핵심 로직 구성이 끝났습니다. 이제 본격적으로 웹 페이지를 구성해 봅시다~
4.3 웹 사이트 구성
CL-WHO를 본격적으로 이용해볼 시간이 왔습니다. 간단하게 한번 CL-WHO를 테스트 해봅시다.
KLISPER> (with-html-output (*standard-output* nil :indent t)
(:html
(:head
(:title "K-Lisper's Books")
)
(:body
(:p "K-Lisper's is best!")
)
)
)
<html>
<head>
<title>
K-Lisper's Books
</title>
</head>
<body>
<p>
K-Lisper's is best!
</p>
</body>
</html>
"
<html>
<head>
<title>
K-Lisper's Books
</title>
</head>
<body>
<p>
K-Lisper's is best!
</p>
</body>
</html>"
표준적으로 사용할 HTML 페이지의 구조를 잡는 Macro를 작성하려고 합니다. 즉, XHTML의 공통적인 요소를 담고 있는 Macro 입니다. 이 Macro를 통하여 불필요한 요소를 없애고 변화되는 내용만 반영하게 만들것입니다.
KLISPER> (defmacro standard-page ((&key title) &body body)
`(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
(:head
(:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
(:title ,title)
(:link :type "text/css" :rel "stylesheet" :href "/retro.css"))
(:body
(:div :id "header" ; K-Lisper's Books Header
(:img :src "/logo.jpg" :alt "K-Lisper" :class "logo")
(:span :class "strapline" "Vote on your favorite Lisp Book"))
,@body))))
STANDARD-PAGE
주의하셔야 할 점은 ,title 와 ,@body 의 , 는 마침표(.)가 아니라 쉼표(,)라는 것입니다.
자~ 첫 페이지인 index.htm을 한번 만들어 봅시다. Macro로 이미 정의한 standard-page의 위력이 발휘되는 순간입니다.
KLISPER> (defun index-page()
(standard-page
(:title "Klisper's Books")
(:h1 "Top KLisper's Books")
(:p "We'll wirite the code later..."
)
)
)
INDEX-PAGE
이제 만든 페이지를 index.htm으로 걸어 봅시다.
KLISPER> (push (create-prefix-dispatcher "/index.htm" 'index-page) *dispatch-table*)
(#<CLOSURE (LAMBDA #) {C70DCED}> DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
웹 브라우져에서 http://localhost:8080/index.htm 이 정상적으로 출력되는지 한번 확인해보세요~
이렇게 등록하는 절차를 더욱 쉽게 하도록 Macro를 사용하겠습니다.
KLISPER> (defmacro define-url-fn ((name) &body body)
`(progn
(defun ,name()
,@body)
(push (create-prefix-dispatcher ,(format nil "/~(~a~).htm" name) ',name)
*dispatch-table*)
)
)
DEFINE-URL-FN
그리고 다시 index 페이지를 작성해보겠습니다.
KLISPER> (define-url-fn (index)
(standard-page (:title "K-Lisper's Book Site")
(:h1 "Top Books!")
(:p "How about it?")
)
)
(#<CLOSURE (LAMBDA #) {BEE6D7D}> #<CLOSURE (LAMBDA #) {BB85A8D}>
DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
웹 브라우져에서 다시 http://localhost:8080/index.htm 을 입력하고 정상적으로 변경되었는지 확인해보세요~ 두번에 해야할 절차를 한번에 간단하게 끝냈습니다. 와우~ 매크로여~ :-)
본격적으로 index.htm 페이지에 투표를 할 수 있는 기능을 추가해보겠습니다.
KLISPER> (define-url-fn (index)
(standard-page (:title "K-Lisper's Top Books")
(:h1 "Vote on your all time favorite Lisp Books!")
(:p "Missing a book? Make it available for votes " (:a :href "new-book.htm" "here"))
(:h2 "Current stand")
(:div :id "chart" ; For CSS Style of Links
(:ol
(dolist (book (books))
(htm
(:li
(:a :href (format nil "vote.html?name=~a" (name book)) "Vote!")
(fmt "~A with ~d votes" (name book) (votes book))
)
)
)
)
)
)
)
STYLE-WARNING: redefining INDEX in DEFUN
(#<CLOSURE (LAMBDA #) {C9B40F5}> #<CLOSURE (LAMBDA #) {D94667D}>
#<CLOSURE (LAMBDA #) {AAB237D}> #<CLOSURE (LAMBDA #) {BB85A8D}>
DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
길어 보이긴 합니다만, 기종의 HTML 작업의 경우 JSP나 PHP 또는 ASP 파일을 각각 만들고 각각 서버에 올려서 다시 컴파일 되는 과정이나 인터프리팅 되는 과정을 거쳐서 결과가 나오지만, Common Lisp의 웹 프로그래밍은 전혀 파일이 필요없습니다.
따라서 언제라도 index.htm 을 바로 변경할 수 있으며 만약 미리 만든 lisp 파일을 load한다고 하여도 파일속의 내용이 현저하게 적습니다. 웹 브라우져로 HTML 소스를 확인해보신다면 아마 팍팍 느끼실 것입니다.
이제 투표 결과를 받는 페이지입니다.
KLISPER> (define-url-fn (vote)
(let ((book (book-from-name (parameter "name"))))
(if book
(vote-for book))
(redirect "/index.htm")
)
)
(#<CLOSURE (LAMBDA #) {D21BE9D}> #<CLOSURE (LAMBDA #) {D9A7965}>
#<CLOSURE (LAMBDA #) {D9B3E6D}> #<CLOSURE (LAMBDA #) {AAB237D}>
#<CLOSURE (LAMBDA #) {BB85A8D}> DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
index.htm에서 Vote! 를 클릭하면 vote.htm으로 왔다가 바로 redirect되어 /index.htm으로 돌아가게 되어있습니다. 여기서 주목할 점은 이전에 만들었던 function인 vote-for를 로직으로 바로 활용했다는 점입니다. 그리고 "name" parameter 역시 간단하게 바로 받아서 처리합니다.
얼마나 멋집니까~ 하하하~ ;-)
이제~ 새로운 책을 추가하는 페이지를 만들겠습니다. 투표할 목록에 원하는 책이 없는 경우 간단하게 책 명을 입력하는 페이지입니다.
KLISPER> (define-url-fn (new-book)
(standard-page (:title "Add a new book!")
(:h1 "Add a new book to the chart")
(:form :action "/book-add.htm" :method "post"
:onsubmit (ps-inline ; Client-side validation.
(when (= name.value "")
(alert "Please enter a name.")
(return false)))
(:p "What is the name of the book?" (:br)
(:input :type "text" :name "name" :class "txt"))
(:p (:input :type "submit" :value "Add" :class "btn"))
)
))
(#<CLOSURE (LAMBDA #) {BC8CD65}> #<CLOSURE (LAMBDA #) {DA52B0D}>
#<CLOSURE (LAMBDA #) {D9A7965}> #<CLOSURE (LAMBDA #) {D9B3E6D}>
#<CLOSURE (LAMBDA #) {AAB237D}> #<CLOSURE (LAMBDA #) {BB85A8D}>
DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
이 페이지에서 드디어 Parenscript 를 사용합니다. 중간에 ps-inline 부터 사용하며, 만약 내용이 없는 경우 다시 입력을 받을 수 있도록 JavaScript로 입력값 검증을 처리하는 부분입니다.
자 새로운 책명을 입력받았다면, 책을 추가하는 페이지입니다. JavaScript에서 이미 걸렸겠지만, 다시 한번 빈 내용이 들어오는지 확인합니다. 이런 처리까지 해주어야 정말 좋은 웹 프로그래밍이라고 생각합니다. 참고로 로직 상으로도 이미 book-stored? function을 통하여 중복 방지 처리가 되어있습니다.
KLISPER> (define-url-fn (book-add)
(let ((name (parameter "name")))
(unless (or (null name)(zerop (length name)))
(add-book name))
(redirect "/index.htm"))
)
(#<CLOSURE (LAMBDA #) {D274E35}> #<CLOSURE (LAMBDA #) {D9999F5}>
#<CLOSURE (LAMBDA #) {DA52B0D}> #<CLOSURE (LAMBDA #) {D9A7965}>
#<CLOSURE (LAMBDA #) {D9B3E6D}> #<CLOSURE (LAMBDA #) {AAB237D}>
#<CLOSURE (LAMBDA #) {BB85A8D}> DISPATCH-EASY-HANDLERS DEFAULT-DISPATCHER)
자~ 이제 index.htm에 있는 here 를 눌러서 새로운 책을 한번 추가해보세요~ :-)
여기까지가 Common Lisp의 새로운 웹 프로그래밍의 세계입니다. 다른 언어도 많은 장점을 제공하지만, Common Lisp이 당근 훌륭한 기능을 제공한다고 생각되네요~
아마 웹 브라우져의 내용이 궁금하신 분들이 많으시리라 생각됩니다. 한번씩 해보세요~ 따라하시기 편하도록 구성하였습니다. ;-)
'Architecture for Software > Lisp' 카테고리의 다른 글
SICP 스터디에 참여해주세요 :-) (2) | 2010.10.18 |
---|---|
Lisp을 좋아하는 사람들의 그룹의 미래와 저의 생각 (0) | 2010.10.04 |
제 1 회 Korea Lisp 세미나 후기 :-) (13) | 2010.05.03 |
드디어 우리나라 첫(?) Lisp 세미나가 열립니다! (6) | 2010.04.25 |
Clojure 개발 환경 구축하기 (6) | 2010.02.17 |