Common Lisp (SBCL) on Windows 7

Language/Common LISP 2011. 10. 14. 12:26 Posted by 알 수 없는 사용자
Common Lisp를 사용하기에 최적의 환경은 역시 리눅스. 최근 우분투가 참 많이 좋아졌고 많은 프로그램들이 리눅스를 지원하니 사용에 심각한 불편함은 없으나 역시 대한민국에서는 윈도우가 편하다.

윈도우 환경에서 리스프를 즐기기 위해서 clojure를 써보는 방법도 있지만 common lisp는 아니기 때문에 좀 애매하다. 돈을 주고 쓸 수 있는 것들은 윈도우용을 제공하지만 무료로 사용할 수 있는 것들 중 쓸만한 것인 clisp나 sbcl은 아직 공식적인 윈도우 버전을 제공하지 않는다. 뭐, 아직 공부 단계이니 돈 주고 사기는 아깝고, 원래 쓰던 sbcl을 사용하기로 했다.

sbcl은 아직까지는 윈도우 환경에서 "Available and Supported" 단계가 아니다. 포팅 중으로 표시되어 있다. 그럼에도 불구하고 설치는 간편하게 할 수 있도록 설치 파일을 제공한다. 인터넷 상의 여러 글에서, 아직은 불안정하다는 이야기를 많이 봤지만 얼마나 불안정한가는 더 사용해봐야 알 수 있겠다. 개인적으로는 간단한 코드를 돌려보는 수준이기 때문에 특별한 버그를 만나진 못했다.

우선 emacs windows 버전은 여기서 다운로드 받을 수 있다. 가장 최근의 bin 버전을 받는 것이 좋다. emacs는 설치파일을 제공하지는 않으며, zip으로 압축되어 있다. 받은 후에는 bin 폴더에 있는 바이너리를 직접 실행하여 사용하면 된다.

SLIME은 이 곳에서 받을 수 있으며 cvs snapshot을 받을 수 있다. SLIME을 다운 받은 후에는 최대한 path 설정이 간편한 곳에 두는 것이 좋다.

SBCL은 여기에서 받을 수 있다. windows 용을 클릭하여 다운로드 후 실행하면 설치된다. 설치할 때 주의할 점은 역시 설치될 경로 설정이다. 최대한 단순하게 하자.

경로를 단순하게 해야 하는 이유는 후에 emacs에서 경로를 설정할 때 공백이 들어간 경로나 긴 이름의 경로를 잘 해석하지 못하는 경우가 발생하기 때문이다.

리눅스에서는 홈 디렉토리에 .emacs에 설정 파일을 두면 되지만 윈도우의 경우에는 어디에 .emacs가 위치할까? 이것을 알 수 있는 간단한 방법은, 이멕스를 실행한 후 Options에서 아무 옵션이나 변경한 후 Save Options를 하면 어느 파일에 저장했는지가 아래 미니 버퍼에 나온다. 보통은 자신의 홈 디렉토리의 AppData/Roaming/.emacs 에 위치한다. 예를 들면 C:/Users/kju/AppData/Roaming/.emacs

.emacs의 위치를 찾았으니 이 파일을 수정하면 된다. 여기에 추가할 코드는 다음과 같다.
(setq inferior-lisp-program ""C:/Users/kju/SBCL/sbcl.exe")
(add-to-list 'load-path "C:/Users/kju/slime-2011-10-13")
(require 'slime)
(slime-setup '(slime-repl))
SBCL과 SLIME의 위치를 지정하는 것이다. 경로가 간단하면 좋은 이유가 여기에 있다. 그리고 경로에 공백이 있을 경우 실행되지 않는 경우가 많다. 경로를 설정할 때 한 가지 주의할 점은 폴더 구분자를 \이 아니라 /를 사용해야 한다는 점이다. 역슬래시를 사용할 경우 escape 문자로 인식하여 오류가 발생한다.

설정 후 M-x slime 해보고 REPL이 정상적으로 실행되면 끝.

ps. 리스프 코딩을 할 때 괄호가 조금 연하게 출력되면 소스코드를 보기에 편하다.
(defface paren-face
  '((((class color) (background dark))
     (:foreground "grey30"))
    (((class color) (background light))
     (:foreground "grey60")))
  "Face used to dim parentheses.")
(add-hook 'emacs-lisp-mode-hook
       (lambda ()
         (font-lock-add-keywords nil
                     '(("(\\|)" . 'paren-face)))))
(add-hook 'slime-mode-hook
       (lambda ()
         (font-lock-add-keywords nil
                     '(("(\\|)" . 'paren-face)))))
를 .emacs에 추가해서 괄호를 연한 회색으로 만들 수 있다. grey뒤의 숫자를 조정하여 입맛에 맞게 설정하자.

[Common Lisp code example] Euclid algorithm: GCD 찾기

Language/Common LISP 2011. 10. 14. 12:05 Posted by 알 수 없는 사용자
두 수의 최대공약수(GCD)를 찾는 유클리드 알고리즘은 널리 알려져있다. 이 알고리즘을 리스프로 옮겨보자. 우선 코드를 보자.
(defun euclid (a b)
  (labels
      ((euclid-gcd (a b)
(if (zerop b)
    a
    (euclid-gcd b (mod a b)))))
  (let* ((gcd (euclid-gcd a b))
  (lcm (/ (* a b) gcd)))
      (values gcd lcm))))
이 코드는 최대공약수 뿐만 아니라 최소공배수까지 얻어내는 함수다. 실행 결과는 다음과 같다.
CL-USER> (euclid 462 1071)
21
23562
두 수를 받아야 하기 때문에 파라미터를 a, b로 둔다. labels는 함수 내에서 이용할 로컬 함수를 다시 정의할 수 있도록 해준다. euclid 함수 내에서 euclid-gcd를 다시 정의한 이유는 GCD를 우선적으로 얻어낸 후 이를 이용해 LCM값까지 출력해주기 위함이다.

euclid-gcd는 두 파라미터 a, b를 그대로 이용해서 recursive 호출을 이용해서 GCD를 얻어낸다. labels로 함수를 정의한 후에는 let* 함수가 나오는데, let*는 로컬 변수를 선언하는 역할을 한다. 위에서 정의한 euclid-gcd를 이용해서 얻는 GCD값을 gcd라는 변수에 넣고, 이 gcd를 이용해서 최소공약수를 얻은 후에 lcm 변수에 할당한다. 얻어낸 gcd값을 바로 아래 라인에서 lcm을 얻는데 사용하기 때문에 let* 함수를 사용한다. 그렇지 않은 경우엔 let을 사용하면 된다.

gcd와 lcm 변수에 각각 최대공약수와 최소공약수를 할당한 후에 이 값을 돌려줘야 되는데 리스프에서는 여러 개의 값을 돌려줄 수 있다. 그 방법이 가장 마지막 줄에 있는 values 함수를 이용하는 것이다.

실제 실행했을 때 21, 23562 두 수가 연속으로 리턴되는 것을 볼 수 있는데, values가 두 값을 넘겨주기 때문이다.

Common Lisp는 Unicode를 처리할 수 있을까?

Language/Common LISP 2011. 10. 14. 12:02 Posted by 알 수 없는 사용자
리스프 관련 책을 읽다가 문자열에 대한 이야기가 나왔다. 문득, 리스프가 유니코드를 처리할 수 있을까 하는 의문이 들었다. 그리고 몇 가지 실험을 해보니 전혀 한글을 해석하지 못한다는 것을 알게 되었다.

하지만 분명 설정의 문제이리라.

"SBCL unicode"로 구글링을 해보니 SBCL은 이미 유니코드에 대한 지원을 하고 있다는 것을 알 수 있었다. 컴파일 때 옵션을 줄 수 있고 하는데 나는 우분투에서 패키지로 깔았으니 옵션을 켜고 컴파일 한 것인지 아닌지 알 수가 없다. 그냥 실험을 해보면 알게 되겠지.

모든 정보는 이 곳에서 얻었다. 결론은 간단하다. 우선 emacs가 기본적으로 유니코드(UTF-8)를 사용하게 하고, SLIME도 유니코드를 사용하게 설정하면 된다는 것.

Emacs의 메뉴바에서
Options -> MULE(Multilingual Environment) -> Set Language Environment -> UTF-8을 선택하고,
Options -> Save Options를 선택해서 저장
이렇게 하면 .emacs의 custom-set-variables에 '(current-language-environment "UTF-8")라는 것이 자동 추가된다. 이멕스의 언어 환경을 UTF-8로 바꿔주는 것이다.

다음으로 SLIME을 설정해야 하는데, .emacs에 다음 한 줄만 추가해주면 된다.
(setq slime-net-coding-system 'utf-8-unix)
이멕스를 다시 실행시켜 환경 변수가 적용되게 한 후, SLIME을 띄우자.
CL-USER> #\한
#\UD55C

CL-USER> "한글"
"한글"
오. 한글을 인식한다.
CL-USER> (char-code #\한)
54620
'한'이라는 글자에 대한 코드를 얻어낼 수 있다.
CL-USER> (code-char 54620)
#\UD55C
역으로 글자로 변환. 이렇게 character code를 보면 재미가 없으니 확실히 확인해보자.
CL-USER> (coerce '(#\UD55C) 'string)
"한"
모든 것이 잘 동작한다. 그렇다면 함수 이름으로 활용해보자.
CL-USER> (defun 안녕 ()
       (format t "안녕하세요.~%"))
안녕
CL-USER> (안녕)
안녕하세요.
NIL
완벽하다.

ps. CLISP를 가지고 실험해보니 CLISP에서도 모두 잘 동작한다.

Common Lisp에서 패키지 다시 컴파일하기.

Language/Common LISP 2011. 10. 14. 11:58 Posted by 알 수 없는 사용자
아. 리스프는 정말 쉽지가 않다. 언어 자체는 너무 마음에 들지만 패키지를 사용하는 것에서 상당히 혼란스럽다. 리스프의 패키지 시스템이 나빠서라고 말하기는 힘들다. ASDF라는 좋은 시스템을 가지고 있다.

단, 문제가 되는 것처럼 보이는 건 common lisp에 대한 구현이 많다는 점이다. Lispworks, Allego CL, SBCL, CLISP, CMUCL 등등. 이들은 물론 표준을 따르고 있지만 모든 구현들이 동일하게 동작하지는 않는다. 그럴 수도 없다. 구현물마다 다른 추가 구현도 가지고 있기도 하고. 이 때문에 다른 사람의 코드를 받아서 사용하려하면 오류가 발생하는 경우가 꽤 있다. 해커들에게는 또 하나의 즐거움일 수도 있지만, 입문자인 나에겐 참 힘든 일이다.

Perl, Python 등의 알려진 언어들은 보통 가장 널리 사용되는 하나의 구현을 가지고 있다. 또한 패키지 관리 시스템도 가지고 있으니 이런 점에서는 매우 편리하다. 괜히 이런 언어들이 많이 사용되는 것이 아니라는 것을 다시 한 번 느꼈다. 많이 사용되니 그만큼 잘 되어 있는 것일 수도 있겠지만.

서론이 길었다. 오늘 여기에 남기려고 하는 팁은 패키지를 다시 컴파일 하는 방법이다. 이걸 찾아보게 된 이유는 bordeaux-threads라는 패키지 때문이다. (이름도 어렵다. 사실 '보르도'라는 지명이야 잘 알고 있지만 불어다보니 참 단어의 철자가 안 외워진다.) 이 패키지를 컴파일 하고 잘 사용하고 있었는데, 무슨 문제가 있었는지, 다른 패키지가 이 패키지를 건드린 것인지, 갑자기 함수 하나가 동작하지 않았다. bordeaux-threads:make-thread 라는 함수이다. 이 함수가 갑자기 undefined라는 것이다. 정의되어 있다고 search는 되는데 실행하면 없는 함수라니.

이 함수를 로드하면 이미 컴파일이 되어 있기 때문에 컴파일된 fasl을 로드하게 되고 문제는 반복되었다. 그렇다면 다시 컴파일 하면 해결되지 않을까 생각했다. 방법은 다음과 같다.
(asdf:operate 'asdf:load-op 'bordeaux-threads :force t)
ASDF를 이용해서 피키지를 로드하는 코드와 거의 같은 코드다. :force라는 키워드를 추가하는 것 뿐이다. 이렇게 하면 처음 패키지를 가져올 때 하는 것처럼 컴파일을 다시 하게 된다.

우분투에서 리스프 패키지 직접 설치하기

Language/Common LISP 2011. 10. 14. 11:54 Posted by 알 수 없는 사용자
이 글이 쓰여지는 현재의 환경은 SBCL, 우분투 10.04이다.

우분투의 시넵틱 패키지 관리자를 이용하면 slime이나 emacs, sbcl 등을 쉽게 설치할 수 있다. cmucl은 현재 - 왜 그러한지 모르겠지만 - 존재하지 않지만 clisp도 제공하고 있다.

우분투의 패키지 관리 시스템에서 common-lisp 관련 라이브러리들은 cl- 로 시작된다. 예를 들면 cl-md5와 같은 패키지가 있으며 이를 설치하면 /usr/share/common-lisp/ 하에 설치되어 활용할 수 있게 된다. 여기에 설치되는 패키지들은 모두 common-lisp-controller가 관리하는데, sbcl을 설치하면 자동으로 이 패키지가 설치될 것이다. 이 패키지의 기능은 어떤 lisp implementation을 사용하든 상관없이 라이브러리를 활용할 수 있도록 하기 위함이다.

sbcl에서는 (require 'package-name)을 하면 사용할 수 있고 clisp와 같은 경우에는 (clc:clc-require 'package-name)을 사용하면 된다. sbcl은 아마 clc-require를 기본으로 hook하고 있는 듯 하다.

문제는 우분투에서 모든 lisp 관련 패키지를 제공하지 않는다는 것에 있다. 누군가가 패키지를 만들어주기를 하릴없이 기다릴 수는 없는 법. 직접 패키지를 다운 받아 설치하는 수밖에. 예를 들면 cl-fad나 hunchentoot와 같은 패키지는 제공되지 않는다.

조금 의외인 것은 직접 설치하는, 이 과정이 생각외로 매우 간단하다는 것이다. SBCL을 사용하기 때문인지는 잘 모르겠으나 아직까지 동작하지 않는 패키지를 만나지 못했다. 이렇게 간단히 문제가 해결되는 이유는 이전에도 이야기한 바 있는 ASDF라는 시스템 때문이다. 대부분의 리스프 라이브러리가 ASDF를 지원하고 있기 때문에 이를 이용하면 쉽게 사용할 수 있게 된다.

방법은
  1. 필요한 라이브러리를 다운로드 한다. 대부분은 .tar.gz으로 묶여있지만 가끔은 cvs로 제공하거나 git을 사용하기도 한다. 어떤 경로든 무관하며 다운로드한 소스코드를 내가 두고 싶은 어느 디렉토리든 두면 된다. 내 경우에는 ~/asdf/ 에 모든 라이브러리 소스코드를 둔다.
  2. 홈 디렉토리에 .clc나 .sbcl이라는 디렉토리를 만든다. clc는 앞서 말한 common-lisp-controller의 약자다. 가능하면 .clc로 만드는 것이 좋을 듯 하다. 그러면 다른 lisp implementation에서도 사용할 수 있기 때문이다.
  3. (.clc로 만들었다고 가정하고) .clc내에 systems라는 디렉토리를 하나 만든다. 결론적으로 ~/.clc/systems/ 를 만드는 것. .sbcl을 만들어도 동일하다. systems라는 디렉토리를 그 안에 만든다.
  4. 이 systems/ 디렉토리 안에서 다운로드한 소스코드 중 한 파일로 심볼릭 링크를 건다. 링크를 걸 파일은 아까 풀어둔 소스코드의 .asd 파일이다. 사실 1번 과정에서 .asd 파일이 있는지 확인하는 것이 좋지만 대부분의 패키지는 .asd를 패키지 이름으로 만들어 두었기 때문에 꼭 확인하지 않아도 된다. 내 경우라면 md5 패키지를 다운로드 했을 경우, ln -s ~/asdf/md5-1.8.5/md5.asd 라고 하면 된다.
  5. 그러면 systems/ 내에 md5.asd 라는 심볼릭 링크가 생겼을 것이다. 이러면 설치가 완료된 것이다. 어떠한 컴파일 작업이나 다른 것이 필요없다. 압축을 풀고, 심볼릭 링크를 걸면 끝이다. 후에 패키지를 제거하고 싶으면 다운 받았던 패키지 디렉토리를 지워버리고 systems/ 에서 심볼릭 링크를 제거하면 된다.
  6. sbcl이 실행된 상태에서 (require 'md5) 라고 하면 첫 로드시에 알아서 패키지를 컴파일 한다. 다음부터는 컴파일 없이 바로 로드된다.
이와 같은 방법으로 어떤 패키지든 쉽게 사용할 수 있다.

clsql을 통해 sqlite3 DB 이용하기

Language/Common LISP 2011. 10. 14. 11:49 Posted by 알 수 없는 사용자
Common Lisp를 이용해서 DB 접속을 하기에 아주 좋은 패키지가 있다. CLSQL이다.

CLSQL을 다운받아 압축을 풀어보면 많은 수의 asd파일이 있음을 알 수 있다. clsql.asd, clsql-uffi.asd는 기본으로 심볼릭 링크를 걸고, 자신이 사용하고자 하는 데이터베이스에 해당되는 asd 파일을 링크걸면 된다.

db2, mysql, odbc, oracle, postgresql, sqlite 등 널리 사용되는 DB는 거의 지원한다. 이 글에서는 sqlite v3를 이용하는 방법을 살펴본다.
(require 'clsql-sqlite3)
명령어를 입력하여 패키지를 로드한다. 문제없이 로드가 되어야 CLSQL을 이용할 수 있다. asdf를 이용해서 로드할 경우에는
(asdf:oos 'asdf:load-op 'clsql-sqlite3)
로 로드할 수 있다.

sqlite3를 이용해서 우선 테스트할 테이블을 하나 만들어보자. 도시들의 온도를 저장하는 간단한 것.
j0nguk@netbook:~$ sqlite3 tem.db
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table cities (id integer primary key, name text, temp real);
sqlite> .tables
cities
sqlite> insert into cities values (null, "seoul", 25.8);
sqlite> insert into cities values (null, "suwon", 26.3);
sqlite> insert into cities values (null, "busan", 23.7);
sqlite> select * from cities;
1|seoul|25.8
2|suwon|26.3
3|busan|23.7
sqlite>
테스트할 데이터베이스를 만들었으니 리스프에서 접근해보자.
CL-USER> (clsql:connect '("/home/j0nguk/tem.db") :database-type :sqlite3)
#<CLSQL-SQLITE3:SQLITE3-DATABASE /home/j0nguk/tem.db OPEN {C5464B9}>
connect 함수는 이름 그대로 데이터베이스에 연결하는 함수다. tem.db라는 파일을 이용해서 sqlite3 DB를 만들었으니 경로를 정확히 지정해준다. 그리고 DB type이 sqlite3라는 것도 명시해준다. 오류없이 위와 유사한 결과가 나오면 연결이 된 것이다.
CL-USER> (clsql:query "select * from cities")
((1 "seoul" 25.8d0) (2 "suwon" 26.3d0) (3 "busan" 23.7d0))
("id" "name" "temp")
바로 쿼리를 보내보자. query라는 함수를 이용하면 된다. 결과는 리스프답게 리스트로 돌아온다. 첫 번째 리턴값은 쿼리에 대한 결과이고 두 번째 리턴값은 column의 이름이다.
CL-USER> (clsql:print-query "select * from cities")
1 seoul 25.8
2 suwon 26.3
3 busan 23.7
; No value
리스트로 돌아오지 않고 스트링으로 보고 싶을 때는 print-query라는 함수를 이용하면 된다.
CL-USER> (clsql:select '* :from 'cities)
((1 "seoul" 25.8d0) (2 "suwon" 26.3d0) (3 "busan" 23.7d0))
("id" "name" "temp")
query 함수는 스트링을 매개변수로 받기 때문에 변수를 쿼리에 이용하거나 할 때는 조금 불편하다. 이때는 select 함수를 이용하면 된다. select외에 SQL 명령어들을 다양한 함수로 지원한다.
CL-USER> (clsql:query "insert into cities values (null, 'daegu', 29.4)")
NIL
NIL
CL-USER> (clsql:select '* :from 'cities)
((1 "seoul" 25.8d0) (2 "suwon" 26.3d0) (3 "busan" 23.7d0) (4 "daegu" 29.4d0))
("id" "name" "temp")
insert 쿼리도 잘 적용되는 것을 볼 수 있다. insert가 실제로 잘 적용되었는지 sqlite3 인터페이스를 통해 확인해보자.
j0nguk@netbook:~$ sqlite3 tem.db
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from cities;
1|seoul|25.8
2|suwon|26.3
3|busan|23.7
4|daegu|29.4
sqlite>
모든 작업이 끝나면 disconnect 함수로 접속을 끊을 수 있다. 연결된 데이터베이스를 확인하는 함수는 connected-databases이다. 연결이 끊어졌기 때문에 NIL이 리턴되는 것을 알 수 있다.
CL-USER> (clsql:disconnect)
T
CL-USER> (clsql:connected-databases)
NIL