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에서도 모두 잘 동작한다.

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