레슨 02. 필수 - 평가(Essential Evaluation)

폼(form)은 평가될 수 있다

폼(form)아톰(atom) 혹은 리스트(list)가 될 수 있습니다. 중요한 것은 평가(evaluation)된다는 것입니다. 평가라는 것은 상당한 기술적 의미를 지니고 있으며, 이번 섹션에서 서서히 그 모습을 드러나게 될 것입니다.

이 아톰이라면 평가는 단순합니다. 리스프는 아톰을 마치 이름처럼 다루고, 값이 존재한다면 이름에 저장된 값을 얻습니다. 여러분은 아마도 왜 제가 아톰은 변수라고 직접적으로 말하지 않는지 의아할 것입니다. 명확하게 정의하지 않은 이유는 아톰은 변수이거나 상수의 값을 가질 수 있기 때문입니다. 그리고 아톰의 값은 경우에 따라 상수가 될 수 도 있습니다.

숫자는 아톰입니다(이 값은 상수입니다). 리스프는 숫자에 값을 저장 할 수 없습니다: 숫자는 자체적으로 평가됩니다.

정의가 완전히 내려지지 않은 새로운 용어를 소개합니다. 이제, 심볼을 값을 가질 수 있는 아톰이라고 여기기 바랍니다. 레슨 5[p 53]에서 심볼에 대해 더욱 자세히 살펴볼 것입니다.

defconstant로 정의된 심볼은 상수 값을 지닙니다. 리스프는 변수처럼 아톰에 값을 저장한 다음, 값을 바꿀 수 없다라는 메모를 추가합니다.

패키지에 있는 키워드(keyword) 심볼은 스스로 평가됩니다. 패키지에 관한 것은 31장[p 247]에서 자세히 살펴볼 것입니다. 지금, 여러분이 알아야 할것은 (패키지 프리픽스라 불리는) : 문자로 시작하는 심볼은 키워드 심볼이라는 것입니다. 키워드 심볼은 그 자신을 값으로 갖습니다.

다양한 방식으로 심볼에서 값을 얻을 수 있습니다. 리스프는 실제로 심볼에 다양한 값을 저장합니다. 하나는 변수로서의 심볼의 값. 그리고 다른 하나는 함수로서 심볼입니다. 또 다른 것들은 해당 심볼에 대한 문서를 얻거나, 출력값으로 활용하거나, 연관리스트(associated list)처럼 속성값으로 쓰이기도 합니다. 이러한 것들에 대해 레슨 5 [p 53], 레슨 6 [p 56], 레슨 7 [p 59]에서 더욱 자세히 살펴볼 것입니다.

폼이 리스트라면, 첫번째 요소는 심볼이거나 람다(lambda)표현식이라 불리는 특별한 폼일 것입니다. (람다 표현식에 대한것은 잠시 뒤로 미루겠습니다.) 심볼은 함수의 이름을 짓습니다. 리스프에서 심볼 +, -, *, /는 일반적인 산술 연산자입니다: 덧셈, 빨셈, 곱셈, 나누기. 각 심볼은 산술 연산을 수행하는 함수와 연관되어있습니다.

따라서 리스프가 폼 (+ 2 3)을 평가하면, 이는 덧셈 함수+에 인자 23을 적용시킬 것이며, 예상되듯이 결과 5를 반환할 것입니다. 함수로서 심볼 +가 인자들 앞에 있습니다. 이는 전위 표기법(prefix notation)입니다. 리스프가 리스트를 폼으로 평가하기 위해 무얼할 것인지 이해하기 위해선, 리스트의 첫번째 요소를 살펴보시기 바랍니다.

함수는 인자를 받을 수 있다

주어진 리스트를 평가할때 리스프는 폼을 함수 호출로써 다룹니다. 지금부터 우리는 수 많은 리스프의 평가를 보게 될 것이며, 리스프의 입력과 이의 반응을 구분하기 위해 다음과 같이 시각적 표시를 할 것입니다:

(어떠한 리스프 입력)
;;=> 리스프 평가의 결과

;;>> 리스프 출력
;;<< 리스프에 입력
;;>| 리스프의 에러 메시지

예:

(+ 4 9)
;;=> 13

(- 5 7)
;;=> -2

(* 3 9)
;;=> 27

(/ 15.0 2)
;;=> 7.5

위 경우에서 보듯이, 평가된 폼은 리스트입니다. 각각의 첫번째 요소는 심볼이자 함수의 이름입니다. 남아있는 요소는 해당 함수의 인자입니다. 여기서, 인자는 모두 숫자이며, 숫자는 스스로 평가된다는 것을 알 수 있습니다.

여기 몇몇 예제가 더 있습니다:

(atom 123)
;;=> T

(numberp 123)
;;=> T

(atom :foo)
;;=> T

(numberp :foo)
;;=> NIL

atomnumberp는 술어(predicate)입니다. 술어는 참 혹은 거짓을 반환합니다. 리스프에서 NIL은 거짓을 나타냅니다. NIL이 아닌 것은 모두 참입니다. 딱히 의미있는 값이 아니면 술어는 관습적으로 참을 의미하는 T를 반환하게 되어 있습니다. atom은 인자가 리스프의 아톰이라면 T를 반환합니다. numberp는 인자가 숫자이면 T를 반환합니다.

위의 폼들을 평가하기 위해, 리스프는 우선 (좌측에서 우측으로) 인자를 평가하고, 그런 다음 첫번째 요소를 평가하여 함수를 얻은 후, 앞선 인자들을 함수에 적용합니다. 몇몇 예외가 있지만, 그것들은 이번 레슨의 끝부분에서 배울 것입니다.

리스프는 리스트 폼을 평가하기 위해 다음과 같은 작업을 수행합니다:

  1. 나머지 요소들을 좌측에서부터 우측으로 인자들을 평가한다.
  2. 첫번째 요소에서 함수를 얻는다.
  3. 함수에 인자들을 적용한다.

아톰 또한 리스프의 폼이라는 것을 명심하시기 바랍니다. 주어진 아톰이 평가되면, 리스프는 아톰이 지닌 값을 반환합니다:

17.95
;;=> 17.95

:A-KEYWORD
;;=> :A-KEYWORD

*FEATURES*
;;=> (:ANSI-CL :CLOS :COMMON-LISP)

"Hello, world!"
;;=> "Hello, world!"

WHAT-IS-THIS?
;;>| Error: Unbound variable

숫자와 키워드는 스스로 평가됩니다. 문자열도 그러합니다. *FEATURES*는 리스프에 의해 미리 정의된 변수입니다. 여러분의 시스템은 아마도 다른 값을 반환할 것입니다.

심볼 WHAT-IS-THIS?는 리스프에 의해 미리 정의되지 않아 값을 지니지 않으며, 이에 값을 얻을 수 없습니다. 시스템은 값 대신에 에러메시지로 응답할 것입니다. 에러 메시지 앞에 ;;>|를 붙여 표시하였습니다. 시스템에 따라 다른 에러 메시지가 출력될 수 있습니다.

함수는 다수의 값을 반환 할 수 있다

우리는 종종 다수의 값을 반환하는 함수를 갖길 원합니다. 예를들어, 데이터베이스 전체를 살펴보는 함수는 요구하는 값과 완료상태코드를 동시에 반환해야 합니다. 이를 행할 방법으로는 해당 결과값을 저장할 위치 자체를 함수에 전달하는 것입니다; 가능은 하지만, 리스프 프로그램에서는 매우 드믄 일입니다.

또 다른 접근법은 결과와 상태코드를 하나로 묶어 하나의 반환 값으로 만드는 것입니다. 리스프는 구조체[p 72]를 포함하여 여러분에게 이를 수행할 다양한 방식을 제공합니다. 다만, 이와같이 하나로 묶는 방식은 잘못하면 가비지(29장 [p 238] 참조)가 생성되어 프로그램 작동 속도가 느려지게 만들 수 있기에 숙련된 리스프 프로그래머는 이와 같은 작업을 피합니다.

함수에서 다수의 값을 반환하는 올바른 법은 values 폼을 이용하는 것입니다. 잠시 후에 함수 컨텍스트안에서의 VALUES의 사용법을 [p 63]에서 살펴보도록 하겠습니다. 지금은, 리스프가 values 폼을 평가할때 무슨 일이 벌어지는지 살펴봅시다:

(values 1 2 3 :hi "Hello")
;;=> 1
;;=> 2
;;=> 3
;;=> :HI
;;=> "Hello"

리스프가 values 폼으로 각 인자에 대한 값을 반환하는 것을 확인할 수 있습니다.

함수 안에서 인자를 수정하지 않는다

앞서, 결과값을 저장할 위치 자체를 인자로 함수에 넘길 수 있으며, 함수가 그 위치의 값을 바꿀 수도 있다고 말한 바가 있습니다. 다른 언어들은 이를 일반적인 레파토리로 말할지라도, 리스프 프로그램에서 매우 드문 일입니다.

저장할 위치에 키워드가 아닌 심볼이나 구조체 같은 것을 넣을 수 는 있습니다. 심볼을 넣을 경우, 함수는 심볼에 새로운 값을 넣는 코드를 수행해야만 합니다. 구조체를 넣을 경우 구조체의 각 값을 올바르게 변경하는 코드를 수행해야만 합니다. 이러한 작업 자체도 복잡하고 이렇게 작성된 프로그램을 이해하는 것도 어렵습니다. 따라서 리스프 프로그래머들은 일반적으로 인자를 수정하지 않고, 인자는 인자, 결과는 결과로 구분된 함수를 작성합니다.

인자는 (보통은) 함수가 적용 전에 평가된다

리스프가 함수를 평가하면, 앞서 봤던것처럼[p 42], 항상 모든 인자를 우선적으로 평가합니다. 불행히도, 모든 규칙에는 예외가 있으며, (곧 보게될 것처럼)이 규칙도 예외는 아닙니다... 문제는 리스프가 함수의 인자를 평가하지 않을 수 있다라는 점이 아니라, 리스트 폼은 함수 호출이 아닐 수 도 있다라는 점입니다.

인자들은 좌측에서 우측으로 순서대로 평가된다

리스트 폼이 함수를 호출하면, 이의 인자는 항상 좌측에서 우측으로 순서대로 평가됩니다.

스페셜 폼과 매크로는 인자의 평가하는 방법을 바꿀 수 있다

리스트 폼이 함수 호출이 아닐 수 도 있다 라고 했는데, 그럼 무엇이 될 수 있을까요? 2가지 경우가 있지만, 결과는 같습니다: 몇몇 인자는 평가되며 몇몇은 평가가 안됩니다. 폼이나 폼이 아니냐에 달려있습니다. 이 예외에 대해서만 알면 됩니다. 다행히도, 대부분의 리스프 시스템은 한두번의 키 입력으로 이에 대한 온라인 문서를 여러분에게 보여줄 것입니다.

모든 인자가 평가되지 않는 폼은 두가지가 있습니다: 스페셜 폼과 매크로. 리스프는 몇몇 스페셜 폼을 미리 정의해 두었습니다. 언어 자체의 주요 기능이기에 여러분만의 스페셜 폼을 추가할 수는 없습니다. 또 리스프는 몇몇 매크로를 미리 정의해두었습니다. 단, 매크로는 저희가 작성할 수 있습니다. 리스프의 매크로를 이용하면 언어의 강력한 힘을 이용하여 우리만의 기능을 추가 할 수 있습니다. 이 장의 뒷 부분에서 간략하게 간단한 매크로를 작성해 볼 것입니다 [p 61]. 20장에서는[p 188] 복잡한 매크로에 대해 다뤄볼 것입니다.

짚고 넘어가기

  • 폼(form)
  • 키워드(:)
  • 전위 표기법(prefix notation)
  • atom
  • numberp
  • NIL
  • T
  • values