레슨 03. 스페셜 폼과 매크로에 대한 예제

이제 스페셜 폼과 매크로에 대해 살펴보도록 하겠습니다. 다음 4개의 레슨들을 거쳐, 가장 기본적인 리스프 데이터 형식, 리스트를 이용하여 간단한 함수를 작성케 해주는 레파토리를 구축할 것입니다. 나머지 장에서는 더욱 복잡한 프로그램 구조와 데이터형식을 다룰 것입니다.

SETQ

이전에, 여러분께 리스프가 심볼 폼을 평가하여 변수의 값을 받온다고 말했습니다. setq는 이 변수의 값을 설정하는 방법을 제공합니다:

(setq my-name "David")
;;=> "David"

my-name
;;=> "David"

(setq a-variable 57)
;;=> 57

a-variable
;;=> 57

(setq a-variable :a-keyword)
;;=> :A-KEYWORD

setq의 첫번째 인자는 심볼입니다. 이는 평가되지 않습니다. 두번째 인자는 변수의 값으로 할당됩니다. setq는 마지막 인자의 값을 반환합니다.

setq는 심볼 그 자체를 값으로 할당하고자 하기에 첫번째 인자를 평가하지 않습니다. 만일 setq가 첫번째 인자를 평가한다면, (할당될 심볼이 있어야 하기에)해당 인자의 값은 심볼이여야 합니다. set 폼이 그러한 일을 합니다:

(setq var-1 'var-2)
;;=> VAR-2

var-1
;;=> VAR-2

var-2
;;>| Error: Unbound variable

(set var-1 99)
;;=> 99

var-1
;;=> VAR-2

VAR-2
;;=> 99

첫번째 폼에서 '을 발견하셨나요? 이것은 다음 폼 var-2 가 평가되는 것을 방지합니다. 이번 레슨 후반에, quote[p 50]를 살펴볼때, 더욱 자세히 설명하도록 하겠습니다.

이번 예제에서는, 우선 var-1의 값을 심볼 var-2로 설정하였습니다. 그 후 var-2의 값을 확인하였고, 아무런 값도 가지지 않았다는 것을 확인하였습니다. 다음으로, (setq가 아닌) set을 이용하여 var-1의 값인 심볼 var-2에 값 99를 할당하였습니다.

사실 setq 폼은 심볼과 값을 번갈아 사용하여 짝수개의 인자를 취할 수 있습니다:

(setq month "June"
      day   8
      year  1954)
;;=> 1954

month
;;=> "June"

day
;;=> 8

year
;;=> 1954

setq는 좌측에서 우측으로 할당을 수행하고, 맨 우측에 있는 값을 반환합니다.

LET

let 폼은 이전에 봐왔던 것보다 좀 더 복잡해 보입니다. let폼은 중첩된 리스트를 이용하지만, 스페셜 폼은 아니기에 특정 요소만 평가됩니다:

(let ((a 3)
      (b 4)
      (c 5))
  (* (+ a b) c))
;;=> 35

a
;;>| Error: Unbound variable

b
;;>| Error: Unbound variable

c
;;>| Error: Unbound variable

위에 있는 let 폼은 심볼 a, b, c의 값을 정의 후, 이를 이용하여 산술 계산을 하였습니다. 또한 이 계산의 결과가 바로 let 폼의 결과입니다. let에서 정의된 변수가 폼을 평가한 후에는 어떠한 값도 지니지 않는다는 점을 주목하시기 바랍니다.

대게, let은 다음과 같이 생겼습니다:

(let (bindings)
  forms)

bindings에는 임의의 수의 두개의 원소를 지닌 리스트가 있으며 (각 리스트는 심볼과 값을 지닙니다), forms에는 임의의 수의 리스프 폼이 있습니다. forms의 평가를 위해, bindings에 의해 수립된 값을 이용합니다. let은 마지막 폼에 의해 반환된 값(들)을 반환합니다.

들여쓰기는 let의 동작에 영향을 미치진 않지만, 적절한 들여쓰기는 가독성을 향상시킵니다. 다음 두 동일한 폼을 살펴보시기 바랍니다:

(let ((p 52.8)
      (q 35.9)
      (r (f 12.07)))
  (g 18.3)
  (f p)
  (f q)
  (g r t))

(let ((p 52.8) (q 35.9) (r (f 12.07))) (g 18.3) (f p) (f q) (g r t))

첫번째 경우, 들여쓰기로 어떤게 바인딩이며 어떤게 폼인지 명확하게 나타납니다. 독자가 let 폼의 두 부분에서 수행된 서로 다른 규칙에 대해 자세히 알지 못할지라도, 들여쓰기는 차이를 나타냅니다.

두번째경우, 여러분은 어디에서 바인딩이 끝나며 폼이 시작되는지 알고자 한다면, 괄호를 세어야만 할 것입니다. 더욱 안좋은 것은, 들여쓰기의 부제는 let 폼의 두 부분에 의해 수행되는 역활의 차이점에 대해 시각적 단서(visual cues)를 없애버립니다.

setq를 이용하여 변수를 정의하고 let 폼에서 동일한 변수 이름을 사용한다면, let을 평가하는 동안 let에 의해 정의된 값이 (setq에 의해 정의된)다른 값을 대체할 것입니다:

(setq a 89)
;;=> 89

a
;;=> 89

(let ((a 3))
  (+ a 2))
;;=> 5

a
;;=> 89

좌에서 우로 순서대로 값의 할당이 이루어지는 setq와 달리, let은 모두 동일한 시간에 변수를 바인드합니다.

(setq w 77)
;;=> 77

(let ((w 8)
      (x w))
  (+ w x))
;;=> 85

letw를 8로 x는 w로 바인드 하였습니다. 동일한 시각에 이러한 바인딩이 발생하였으므로 w는 여전히 값 77을 지니게 됩니다.

리스프는 순서대로 바인딩을 수행하는 let*이라는 let의 변종을 지녔습니다.

(setq u 37)
;;=> 37

(let* ((v 4)
       (u v))
  (+ u v))
;;=> 8

COND

cond 매크로는 조건적으로 리스프 폼을 평가하도록 합니다. let처럼, cond는 폼의 다양한 부분을 구분짓기 위해 괄호를 이용합니다. 이 예제를 살펴보시기 바랍니다:

(let ((a 1)
      (b 2)
      (c 1)
      (d 1))
  (cond ((eql a b) 1)
        ((eql a c) "First form" 2)
        ((eql a d) 3)))
;;=> 2

위에 정의된 cond 폼에서 3개의 절을 정의하였습니다. 각 절은 테스트 폼으로 시작하는 리스트이며 원하는 만큼의 바디(body) 폼이 뒷따라 나옵니다. 바디 폼은 테스트가 성공일시 실행되는 코드 입니다. 순차적으로 절이 선택됩니다 - 하나의 테스트가 성공하면 그에 대응하는 바디 폼이 평가되고 그 바디 폼의 마지막 값이 cond 폼의 값이 됩니다.

cond는 여러 절을 다룰 수 있기에, 스페셜 폼 if에 비해 범용적입니다.

이제 예제에서 어떤일이 수행되는지 살펴보도록 하겠습니다. 두 인자가 동일하거나, 동일한 숫자면 eqlT를 반환합니다(17장[p 174]에서 다루게될 미묘한 다름이 있긴 합니다). 3개의 테스트중 두개만 실행되었습니다. 첫번째 (eql a b)NIL을 반환합니다. 그러므로, 1을 포함하는 절은 넘어갑니다. 두번째 절은 (eql a c)를 테스트하며 이는 참입니다. 이 테스트가 NIL이 아닌 값을 반환하기에, 절의 나머지 부분이 평가가 되어, 마지막 폼의 값이 cond의 값으로 반환된 다음, 최종적으로 let의 반환값으로써 반환됩니다. 세번째 절은 이미 이전 절이 선택되었기에 평가되지 않습니다 - 절들은 순서대로 선택됩니다.

관습적으로 cond의 마지막 절의 테스트 폼으로 T를 사용합니다. 이는 다른 절들이 모두 테스트에 실패를 하면 마지막 절의 바디 폼이 평가된다는 것을 보증합니다. 기본 값을 반환하거나 기타 다른 적절한 작업을 수행하기 위해 이 마지막 절을 활용할 수 있습니다. 여기 예제가 있습니다:

(let ((a 32))
  (cond ((eql a 13)
         "An unlucky number")
        ((eql a 99)
         "A lucky number")
        (t
         "Nothing special about this number")))
;;=> "Nothing special about this number"

QUOTE

가끔씩 리스프의 평가 규칙을 적용시키지 않고 싶을 때가 있을것입니다.. 이러한 예 중 하나를 꼽자면, 함수 호출의 인자로써 심볼의 값보다 심볼 그 자체를 쓰고 싶을 때입니다:

(setq a 97)
;;=> 97

a
;;=> 97

(setq b 23)
;;=> 23

(setq a b)
;;=> 23

a
;;=> 23

(setq a (quote b))
;;=> B

a
;;=> B

차이점은 (setq a b)에서 사용된 b(setq a (quote b))에서의 심볼 b입니다.

quote 폼은 매우 자주 사용되며, 리스프는 다음과 같은 표기법를 제공합니다:

(QUOTE form) == 'form

리스프는 리더 매크로를 통해 'quote를 동일하게 처리합니다. 레슨 12 [p 82]에서 어떻게 여러분만의 리더 매크로를 정의할 수 있는지 간략하게 살펴볼 것입니다.

짚고 넘어가기

  • setq
  • let
  • cond
  • ', quote