Deute's RSS List
Casting SPELs in LISP
http://www.lisperati.com/casting.html이라는 사이트가 있다. LISP에 대해 처음 감을 잡을 때 가장 좋은 사이트가 아닌가 싶다. 더 이야기할 것 없이 매우 훌륭한 내용인데 굳이 사족을 붙이는 이유는 한글로 된 설명을 참고로 보면 이해가 더 빠르리라는 기대 때문이다. 리눅스나 기타 환경에서 사용하는 사람들이라면 알아서 잘 하실 것 같고 윈도에서 한다면 다음의 환경이 무난하다고 생각한다.
http://www.franz.com/ftp/pub/acl81express/windows/lispbox07.exe
일단 리습은 REPL이라는 환경이 갖추어져 있어 직접 한줄 씩 입력하다 보면 프로그램이 완성된다. 물론 유지보수를 위해서는 파일을 열어 소스코드를 저장해야겠지만 학습을 하는데는 하나씩 입력해보는 것이 최선이다.
(setf cl:*print-length* nil)
이건 franz사의 리습에서 메시지 길이 제한을 없애는 것 이걸 하지 않으면 blah blah blah … 로 생략되어 표시된다.
(setf *objects* '(whiskey-bottle bucket frog chain))
setf가 일반적인 언어의 대입문이라고 생각하면 된다. 바로 뒤에 위치한 *objects*가 변수명이고 ‘뒤에 있는 내용이 변수에 대입될 데이터다. LISP은 (명령어 인자1 인자2 인자3…)나 (데이터1 데이터2 …)로 구성된다고 보면되는데 리스트(지금 생각하는 리스트. 그거다. 그 자료구조에서 배우던 리스트다.)의 앞에 ‘가 없으면 첫번째를 명령어로 보고 ‘가 있으면 데이터로 본다고 생각하자.
또 특이한 점이 변수명 앞뒤로 *가 붙어있다는 건데 일반적으로 말하는 전역변수이다. 글로벌 변수라고 앞에 g_를 붙이는 식과 다름이 없다.
풀이하면 objects들이 있는데 내용이 whiskey-bottle, bucket, frog, chain이라는 것이다. 별 것 없다. 물건을 정의해둔 것이다.
(setf *map* '((living-room (you are in the living-room of a wizards house.
there is a wizard snoring loudly on the couch.)
(west door garden)
(upstairs stairway attic))
(garden (you are in a beautiful garden.
there is a well in front of you.)
(east door living-room))
(attic (you are in the attic of the wizards house.
there is a giant torch in the corner.)
(downstairs stairway living-room))))
괜히 어려워 보이는데 나눠서 보자.
(living-room (you are in the living-room of a wizards house.
there is a wizard snoring loudly on the couch.)
(west door garden)
(upstairs stairway attic))
(living-room A B C)라고 생각하면 되는데 A가 (you… ) B가 (west…) C가 (upstairs…)인 것이다. 리스트 안에 리스트가 있는 것이라고 생각하면 된다. living-room으로 시작하는 리스트, garden으로 시작하는 list, attic으로 시작하는 리스트가 각기 묶여서 *map*이라는 전역 변수에 들어가는 것이다.
내용을 살펴보면 장소인 living-room이 있고 (you..) 부분은 현재 위치를 설명하는 것이라고 볼 수 있다. B와 C는 방향, 물체, 공간이 있는데 이건 west 방향에 door를 열고 지나가면 garden이 있다는 것이다. C의 구성역시 유사하다. 윗층으로 올라가는 계단이 있고 다락이 있다. garden과 attic모두 비슷한 구성을 가지고 있다.
map이 위치, 그 위치에 대한 설명, 다른 위치로 가는 길과 관문을 설명해주는 것이다.
(setf *object-locations* '((whiskey-bottle living-room)
(bucket living-room)
(chain garden)
(frog garden)))
*object-locations*는 (물건과 장소)를 담은 리스트가 연속으로 배치되어 있다.
(setf *location* 'living-room)
*location*은 현재 위치 living-room이다. ‘을 붙인 것은 역시나 코드가 아닌 데이터로 취급받기 위함.
(defun describe-location (location map) (second (assoc location map)))
이제 함수 생성으로 왔다 (defun 함수명 (인자들) 함수내용)으로 함수는 구성된다. describe-location이라는 이름의 함수는 (location map) 두개를 인자로 받고 (second (assoc location map)을 실행하는 것이다.
(assoc A B)은 리스트 B에 포함된 리스트들을 돌면서 A로 시작되는 녀석을 리턴한다. (assoc ‘living-room *map*)이라고 적었다고 치면 ‘living-room으로 시작하는 리스트가 *map*에 있었으니 리턴된다.
(living-room (you are in the living-room of a wizards house.
there is a wizard snoring loudly on the couch.)
(west door garden)
(upstairs stairway attic))
간단하다. 이게 그대로 (second A)에 들어가는데 (second A)는 딱 리스트에 두번째 원소만 리턴하는 애다. living-room이 첫번째 애니깐 (you are..)가 리턴된다. living-room에 대한 설명이 리턴되는 것. 말 그래도 장소 설명하는 애다.
(defun describe-path (path) `(there is a ,(second path) going ,(first path) from here.))
describe-path니깐 경로를 설명하는 함수일텐데 여기서 특이한 점은 `와 ,이다. ‘가 붙으면 전부다 데이터 취급을 받았다. `가 붙으면 다음의 내용은 데이터인데 ,이후의 내용은 코드로 취급된다. 즉 there is a going frome here.부분은 데이터고 (second path)와 (first path)는 해당 코드를 수행하게 되는 것이다. 희안하게 코드와 데이터가 섞여있다. 결국 출력 값은 (there is a … path의 두번째 원소 … going … path의 첫번째 원소 … from here.)가 될 것이다.
(defun describe-paths (location map) (apply #'append (mapcar #'describe-path (cddr(assoc location map)))))
가장 안에 있는 녀석 부터 한 녀석씩 패보자. (assoc location map)이야 map에서 location이 첫번째 원소로 들어 있는 리스트를 반환할 것이다.
cddr을 설명하기 위해서는 car과 cdr을 알아야 한다. (car ‘(1 2 3))이 있다면 리턴 값은 1이다. (cdr ‘(1 2 3))은 리턴 값이 (2 3)이다. 즉 car은 첫번째 원소만 반환하고 cdr은 첫번째 원소만 제외한 리스트를 반환한다. cadr은 cdr을 처리한 후 car을 처리하는 것이다. (car ‘(1 2 3))이라면 먼저 (2 3)이 나온 후 car 처리에 의해 2가 나오게 된다. (즉 (second ‘(1 2 3))과 (cadr ‘(1 2 3))은 동일하다.) cdar은 반대로 car을 처리한 후 cdr을 처리한다. (cdar ‘((1 2 3) 4 5)가 있다면 car에 의해서 (1 2 3)이 나오고 cdr에 의해 (2 3)이 리턴되는 것이다. cddr은 말그대로 cddr을 연속으로 처리하는 것. 앞의 두개의 원소를 버린 리스트를 리턴한다.
assoc을 이용하여 map에서 가져오면 (해당장소 설명 이동경로 이동경로 …)이렇게 아노는데 해당장소와 설명을 날려버리고 이동경로만 검색하겠다는 것이다. ((방향 관문 장소) (방향 관문 장소)… )이런 목록이 리턴될 것이다.
다음으로 (mapcar #’a b)라는 것을 볼 수 있는데 a함수를 b리스트에 있는 모든 원소에 적용하겠다는 무지막지하게 무식한 명령이 되겠다. #’은 다음에 위치하는 애가 함수이름이라는 것을 설명하는 것이다. 알다시피 아까 처리한 결과는 (이동경로1 이동경로2 이동경로3)이 연속으로 들어있는데 이동경로1에 대해서 describe-path를 수행하고 2에 대해서도 수행하고 끝까지 수행할 것이다. describe-path는 (there is 어쩌구)라는 리스트의 형태로 해당 경로를 설명해주는 문자열이 담아준다. ((경로설명) (경로설명) (경로설명)) 이런식으로 리턴될 것. 다음으로 수행되는 (apply #’a b)는 b의 내용을 하나씩 포함하여 실행하는 것이다. append는 결국 리스트에 다른 리스트의 ‘내용’을 원소를 추가하는 것이다. ((경로설명1) (경로설명2) (경로설명3) ….)이라는 리스트는 (경로설명1)이 리스트에 추가되어 (경로설명1)이 될 것이며 (경로설명2)가 리스트에 추가되어 (경로설명1 경로설명2)가 되고 결국 안에 있는 괄호를 까는 형태가 된다. 이 긴 함수들은 describe-path을 이용하여 처리한 결과들을 다 묶어주는 기능을 한다.
(defun is-at (obj loc obj-loc) (eq (second (assoc obj obj-loc)) loc))
eq a b는 a와 b가 같은지 확인하여 준다. 여기선 어떤 물건이 어느 위치에 있는지 확인한 후 그 위치를 현재 위치와 비교하기 위해 사용될 것이다. 자세한 용법은 REPL에서 확인해보면 된다. 내용이 어려우면 (assoc obj obj-loc)을 하나씩 실행해보고 (second ..)를 씌어본다. obj와 obj-loc이 실제 전역변수나 ‘명칭으로 바꿔야 하는 것은 당연.
(defun describe-floor (loc objs obj-loc)
(apply #'append (mapcar (lambda (x)
`(you see a ,x on the floor.))
(remove-if-not (lambda (x)
(is-at x loc obj-loc))
objs))))
여기서 특이한 점이 lambda 라는 부분이다. lambda는 익명 함수를 만드는것. (defun name (parm …) body)가 명칭이 있는 함수라면 (lambda (parm …) body)는 익명 함수이다. 익명 함수는 저렇게 내부적으로 쓰거나 일회성을 가진 함수로 사용되기 위해 사용된다.
(remove-if-not a b)는 b 리스트의 원소를 a 함수에 테스트하여 거짓인 경우 제거하는 것이다. 특정 위치(loc)에 있던 물건들을 확인한 후 그 물건 목록(obj-loc)에 포함되지 않는 애(objs)는 일단 제거하는 것이다. remove-if-not은 실제 변수의 내용의 변화없이 변경된 내용을 리턴으로 전달하니깐 혹시 지워지는게 아니냐 우려할 필요는 없다. 그 변경된 목록은 mapcar로 전달되고 람다에 의해 아 floor에 그 물건이 있더라는 문장으로 포장된다. 마지막으로 apply #’append로 하나의 긴 리스트로 정리한다.
(defun look ()
(append (describe-location *location* *map*)
(describe-paths *location* *map*)
(describe-floor *location* *objects* *object-locations*)))
이것도 사실 별 것없다. 세개의 describe 함수를 호출하여 받은 세개의 리스트가 포함된 하나의 리스트. 그걸 하나의 리스트로 append 시킨 것이다. 아주 긴 설명 문자열이 나올 것이다.
(defun walk-direction (direction)
(let ((next (assoc direction (cddr (assoc *location* *map*)))))
(cond (next (setf *location* (third next)) (look))
(t '(you cant go that way.)))))
walk direction이라는 함수인데 이번에 보게되는 새로운 요소가 두가지가 있다. let과 cond이다. (let ((변수명 내용) …) body)로 구성되는데 지역변수를 할당하는 것이다. next라는 지역변수를 assoc 절로 할당하는데 자세한 내용은 직접 수행해보길 바란다. cond는 다음과 같은 형태로 되어 있다. (cond (조건 body) (조건 body) (조건 body) …) 참고로 참과 거짓은 t와 nil(혹은 () 이다)로 되어 있다. next가 참이면 setf 부분과 look이 연속으로 수행되며 거짓이라면 이제는 참(t)절인 you cant 부분이 출력된다.
(defmacro defspel (&rest rest) `(defmacro ,@rest))
어려우니깐 자세한 설명은 생략한다 간단히 얘기하자면 defmacro라는 매크로 명령이 있는데 이제부터 defspel이라고 적어도 동일하게 수행된다는 것이다.
(defspel walk (direction)
`(walk-direction ',direction))
우리가 (walk direction)이라고 적으면 (walk-direction ‘direction)으로 처리해준다는 것이다. 인자로 전달한 direction은 ,코드 모드로 전달되었고 앞에 ‘라는 글자가 찍히게 된다. 이런 매크로를 이용하는 이유는 (walk-direction ‘west)보다 (walk west)가 더 쿨하기 때문. 딴 이유는 없다.
(defun pickup-object (object)
(cond ((is-at object *location* *object-locations*)
(push (list object 'body) *object-locations*)
`(you are now carrying the ,object))
(t '(you cannot get that.))))
다른 부분에 대한 설명은 생략하고 push 부분만 언급하자면 (object ‘body)라는 리스트를 *object-locations*의 제일 앞에 끼워 넣는 것이다.
(defspel pickup (object) `(pickup-object ',object))
walk랑 비슷한 역할의 매크로
(defun inventory ()
(remove-if-not (lambda (x)>
(is-at x 'body *object-locations*))
*objects*))
설명이 딱히 필요없는 부분이다.
(defun have (object) (member object (inventory)))
inventory 함수를 실행한 후 object가 있는지 확인한다.
(setf *chain-weled* nil)
(defun weld (subject object)
(cond ((and (eq *location* 'attic)
(eq subject 'chain)
(eq object 'bucket)
(have 'chain)
(have 'bucket)
(not *chain-weled*))
(setf *chain-weled* 't)
'(the chain is now securely weled to the bucket.))
(t '(you cannot weld like that.))))
(setf *bucket-filled* nil)
(defun dunk (subject object)
(cond ((and (eq *location* 'garden)
(eq subject 'bucket)
(eq object 'well)
(have 'bucket)
*chain-weled*)
(setf *bucket-filled* 't) '(the bucket is now full of water))
(t '(you cannot dunk like that.))))
더 이상의 자세한 설명은 생략한다.
(defspel game-action (command subj obj place &rest rest)
`(defspel ,command (subject object)
`(cond ((and (eq *location* ',',place)
(eq ',subject ',',subj)
(eq ',object ',',obj)
(have ',',subj))
,@',rest)
(t '(i cant ,',command like that.)))))
지금 부분이 특이한 부분인데 weld, dunk 등의 함수가 중복된 코드가 많아 매크로를 이용하여 빌더를 만든 것이다. game-action이라는 매크로는 command란 이름의 새로운 매크로를 만들어내도록 되어 있다. `가 데이터 모드로 진입 ,가 코드모드로 진입 ‘를 붙여 나중에 해당 부분이 데이터모드로 실행되도록 해두었다. &rest rest인자 부분은 ,@,’rest라는 형태로 그대로 전달되게 되어 있다.
(game-action weld chain bucket attic
(cond ((and (have 'bucket) (setf *chain-weled* 't))
'(the chain is now securely weled to the bucket.))
(t '(you do not have a bucket.))))
(game-action dunk bucket well garden
(cond (*chain-weled* (setf *bucket-filled* 't)
'(the bucket is now full of water))
(t '(the water level is too low to reach.))))
(game-action splash bucket wizard living-room
(cond ((not *bucket-filled*) '(the bucket has nothing in it.))
((have 'frog) '(the wizard awakens and sees that you stole his frog.
he is so upset he banishes you to the
netherworlds- you lose! the end.))
(t '(the wizard awakens from his slumber and greets you warmly.
he hands you the magic low-carb donut- you win! the end))))
game-action을 이용하여 weld, dunk를 재정의하였고 splash를 만들었다. 이로써 게임에 사용되는 모든 함수는 구현되었으며 REPL 창 상에서 적절한 명령을 내리는 방법을 통해 게임을 플레이할 수 있다.
- Blog URL
- (defun world() 'hello world)
- Date
- 2009-07-27 07:40 pm
- 태그
- LISP
이전 목록 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 ... 2701 다음 목록



![Validate my RSS feed [Valid RSS]](http://mydeute.com/images/valid_rss.gif)




