평가
평가 (Evaluation)
평가는 여러 맥락에서 일어날 수 있어요:
- 인터랙티브하게, REPL에서
- 스트림에서 읽은 폼들의 시퀀스에 대해,
load/load-file/load-reader/load-string를 통해 - 프로그래밍 방식으로,
eval을 통해
Clojure 프로그램은 표현식으로 구성돼요. 특수 폼(special form)이나 매크로가 특별히 처리하지 않는 모든 폼은 컴파일러가 표현식으로 간주하며, 값으로 평가되어 산출돼요. 선언(declaration)이나 문(statement)은 없지만, 때때로 표현식이 부수 효과 때문에 평가되고 그 값은 무시될 수도 있어요. 모든 경우에 평가는 동일해요 — 컴파일러가 단일 객체를 고려하고, 평가하며, 그 결과를 반환해요. 표현식이 컴파일되어야 하면 컴파일돼요. 별도의 컴파일 단계가 없고, 정의한 함수가 해석(interpreting)되고 있을까 걱정할 필요도 없어요. Clojure에는 인터프리터가 없어요.
문자열, 숫자, 문자, true, false, nil 그리고 키워드는 자기 자신으로 평가돼요.
심볼은 해석(resolve) 돼요:
- 네임스페이스로 한정(qualified)되어 있으면, 그 값은 심볼이 가리키는 전역 var의 바인딩 값이에요. 심볼이 가리키는 전역 var가 없거나, 다른 네임스페이스의 public이 아닌 var를 참조하면 오류예요.
- 패키지로 한정되어 있으면, 그 값은 심볼이 가리키는 Java 클래스예요. 심볼이 가리키는 Class가 없으면 오류예요.
- 한정자가 클래스를 가리키고, 이름이 다음과 같으면:
** 그 클래스의 정적 필드(static field)라면, 값은 정적 필드의 값이에요.
** 그 클래스의 정적 메서드라면, 값은 정적 메서드를 호출하는 Clojure 함수예요. (1.12부터)
** 그 클래스의 인스턴스 메서드를 접두사로
.이 붙으면, 값은 인스턴스 메서드를 호출하는 Clojure 함수예요. (1.12부터) **new라면, 값은 생성자를 호출하는 Clojure 함수예요. (1.12부터) - 한정자가 클래스나 원시 타입을 가리키고, 심볼 이름이 다음과 같으면:
**
1-9라면, 값은 요소 타입(component type)이 그 한정자이고 숫자에 해당하는 차원(dimension)을 가진 배열 클래스(array class)이에요. (1.12부터) - 그 외에는 한정되지 않은 것이고, 심볼 이름에 다음 중 첫 번째가 적용돼요:
- 특수 폼을 가리키면 특수 폼으로 간주되며, 그에 따라 사용되어야 해요.
- 지역 범위(예: 함수 정의나 let 폼 안)에 있으면, 지역 바인딩(예: 함수 인자나 let으로 바인딩된 이름)을 가리키는지 조회해요. 그렇다면 값은 지역 바인딩의 값이에요.
- 현재 네임스페이스에서 심볼에서 클래스로의 매핑이 있는지 조회해요. 있으면 심볼이 Java 클래스 객체를 가리키는 것으로 간주돼요. 클래스 이름은 보통 클래스 객체를 나타내지만, 특정 특수 폼(예:
.과new)에서는 특별히 취급돼요. - 현재 네임스페이스에서 심볼에서 var로의 매핑이 있는지 조회해요. 있으면 값은 심볼이 참조하는 var의 바인딩 값이에요.
- 오류예요.
심볼에 메타데이터가 있으면 컴파일러가 사용할 수 있지만, 결과 값의 일부가 되지는 않아요.
벡터, 집합, 맵은 그 내용이 포함된 객체들의 평가된 값 인 벡터·(해시) 집합·맵을 산출해요. 벡터 요소는 왼쪽에서 오른쪽으로 평가되고, 집합과 맵은 정의되지 않은 순서로 평가돼요. 메타데이터 맵도 마찬가지예요. 벡터나 맵에 메타데이터가 있으면, 평가된 메타데이터 맵이 결과 값의 메타데이터가 돼요.
user=> (def x 1)
user=> (def y 2)
user=> ^{:x x} [x y 3]
^{:x 1} [1 2 3]
빈 리스트 ()는 빈 리스트로 평가돼요.
비어 있지 않은 리스트는 특수 폼, 매크로, 또는 함수에 대한 호출(call) 로 간주돼요. 호출은 +(operator operands*)+ 형태예요.
특수 폼은 핵심 연산을 수행하는 Clojure에 내장된 원시 형태예요. 호출의 연산자(opertor)가 특수 폼 이름으로 해석되는 심볼이라면, 그 호출은 그 특수 폼에 대한 호출이에요. 각 폼은 특수 폼에서 개별적으로 다뤄져요.
매크로는 형태(form)를 조작해 구문적 추상화를 가능하게 하는 함수예요. 호출의 연산자가 매크로 함수인 전역 var를 가리키는 심볼이라면, 그 매크로 함수가 호출되고 평가되지 않은 피연산자 폼들이 전달돼요. 매크로의 반환 값은 그 자리에서 평가돼요.
연산자가 특수 폼이나 매크로가 아니면, 그 호출은 함수 호출로 간주돼요. 연산자와 피연산자(있으면) 모두 왼쪽에서 오른쪽으로 평가돼요. 연산자 평가의 결과는 IFn(Clojure 함수를 나타내는 인터페이스)으로 캐스팅되고, 평가된 인자를 전달해 invoke()가 호출돼요. invoke()의 반환 값이 호출 표현식의 값이에요. 함수 호출 폼에 메타데이터가 있으면 컴파일러가 사용할 수 있지만, 결과 값의 일부가 되지는 않아요. 특수 폼과 매크로는 특수 폼에서의 설명처럼 인자의 비정상적(non-normal) 평가를 가질 수 있다는 점에 주의하세요.
위에서 논의한 것 외의 다른 객체는 자기 자신으로 평가돼요.
(load 클래스패스 리소스 ...) +
(load-file 파일이름) +
(load-reader reader) +
(load-string 문자열)
위 내용은 단일 폼의 평가를 설명해요. 여러 load 폼은 소스에 들어 있는 폼 집합을 순차적으로 읽고 평가해요. 이런 폼 집합은 보통 부수 효과를 가지며, 종종 전역 환경에 함수를 정의하는 등의 영향을 줘요.
로딩 함수들은 임시 컨텍스트에서 발생하며, 그 컨텍스트에서 pass:[ns]는 새로운 바인딩을 가져요. 이는 어떤 폼이 그 var에 영향을 주어도(예: +in-namespace+) 그 영향이 로드 완료 시 되감기(unwind)된다는 뜻이에요. load 등은 마지막 표현식이 만든 값을 반환해요.
(eval form) +
폼 데이터 구조(텍스트가 아니라!)를 평가하고 그 결과를 반환해요.
(eval (list + 1 2 3))
-> 6
출처: 평가 (Evaluation)