스펠 외우기
자, 이제 리스프의 놀랍도록 강력한 기능을 배워보겠습니다: SPEL 만들기!
스펠(마법, SPEL
)은 S
emantic P
rogram E
nhancement L
ogic의 줄임말로, 필요에 맞게 리스프 언어 자체를 변경할 수 있게 만들어 줍니다. 리스프에서 가장 마법처럼 보이는 부분입니다.
스펠을 사용하려면 먼저 리스프 컴파일러 내에서 스펠을 활성화 시켜야 합니다.
(defmacro def-스펠 [& rest]
`(defmacro ~@rest))
좋습니다. 이제 활성화 되었습니다. 이제 첫 스펠이동
을 외워봅시다:
(def-스펠 이동 [방향]
`(방향으로걷기! ~(keyword 방향)))
keyword
- ex)
(keyword '서쪽)
=>:서쪽
- ex)
이 코드가 하는 일은 리스프 컴파일러에게 이동
라는 단어가 실제로는 방향으로걷기!
라는 단어이며, 내부적으로 인자로 들어온 방향(ex 서쪽
)을 키워드방향(ex :서쪽
)으로 만들어 줍니다.
코드를 완전히 컴파일하기 전에 다른 것으로 변경하는 특수한 코드를 프로그램과 컴파일러 사이에 몰래 삽입할 수 있습니다:
리스프에서는 코드와 데이터가 거의 동일하게 보입니다. 매우 일관되고 깔끔한 디자인입니다! 새로운 스펠을 사용해 봅시다:
> (이동 서쪽)
;; (방향으로걷기! :서쪽)
["[아름다운 `정원`]: `우물`이 앞에 보인다"
"`동쪽`으로 가는 문이 있다"
"`개구리`(이/가) 바닦에 있다"
"`사슬`(이/가) 바닦에 있다"]
훨씬 나아졌습니다!
이제 게임 세상에서 오브젝트를 집는 함수를 만들어 보도록 하겠습니다:
(defn 오브젝트-집기! [오브젝트]
(let [오브젝트이름 (name 오브젝트)]
(if-not (오브젝트가-해당-장소에있는가? @atom_플레이어_사전_오브젝트_랑_장소 오브젝트 @atom_플레이어_현재장소)
[(format "여기에는 `%s`(이/가) 없습니다" 오브젝트이름)]
(do
(swap! atom_플레이어_사전_오브젝트_랑_장소 assoc 오브젝트 :주인공-인벤토리)
[(format "`%s`(을/를) 집어들었습니다" 오브젝트이름)]))))
이 함수는 오브젝트가 실제로 현재 위치의 바닥에 있는지 확인하고, 만약 그렇다면 atom_플레이어_사전_오브젝트_랑_장소
를 갱신하고, 성공 여부를 알려주는 문장을 반환합니다.
갱신할때 앞서 배운 swap!
함수를 활용했습니다.
atom_플레이어_사전_오브젝트_랑_장소
에 assoc
함수를 적용시키며, 인자로는 오브젝트
와 :주인공-인벤토리
를 넘겨주었습니다.
이 함수도 앞선 방향으로걷기!
와 같이 키워드를 인자로 받기에, 이동
스펠처럼 좀 더 쉽게 사용할 수 있는 다른 스펠을 시전해 보겠습니다:
(def-스펠 집어들기 [오브젝트]
`(오브젝트-집기! ~(keyword 오브젝트)))
이제 새로운 스펠을 사용해 봅시다:
> (집어들기 위스키)
["`위스키병`(을/를) 집어들었습니다"]
이제 유용한 함수 몇개 더 추가해 보도록 하겠습니다. 우선 현재 플레이어가 가지고 있는 오브젝트 목록을 볼 수 있는 함수를 만들어 보겠습니다:
(defn 플레이어-오브젝트-리스트-가져오기 []
(filter #(오브젝트가-해당-장소에있는가? @atom_플레이어_사전_오브젝트_랑_장소 % :주인공-인벤토리) 상수_리스트_모든오브젝트))
이제 플레이어가 특정 오브젝트를 가지고 있는지 알려주는 함수를 만들어 보겠습니다:
(defn 가지고있는가? [오브젝트]
(->> (플레이어-오브젝트-리스트-가져오기)
(some #{오브젝트})
(some?)))
some
과 some?
함수가 보입니다. 이 둘의 차이점은 무엇일까요?
some
: 함수를 콜렉션의 각 요소에 적용하여, 평가 결과가 nil/fase가 아닌게 나오면 그 평가결과를 반환합니다.- ex)
(some even? '(1 2 3 4))
=>true
,(some even? '(1 3))
=>nil
- ex)
some?
: nil이면 false, 아니면 true- ex)
(some? nil)
=>false
- ex)
some함수의 반환값이 nil
이 나올 수 있기에, 마지막에 some?
을 사용하여 true/false
로 변환해 주었습니다.