Var와 전역 환경
Var와 전역 환경 (Vars and the Global Environment)
Clojure는 변화하는 값에 대한 영속 참조를 유지할 필요가 가끔 있음을 인정하는 실용적인 언어이며, 그것을 통제된 방식으로 수행하기 위한 4가지 별개의 메커니즘을 제공해요 — Var, Refs, Agents, Atoms. Var는 스레드별로 (새 저장 위치로) 동적으로 다시 바인딩될 수 있는 변경 가능한 저장 위치를 참조하는 메커니즘을 제공해요. 모든 Var는 (필수는 아니지만) 루트 바인딩(root binding)을 가질 수 있으며, 그것은 스레드별 바인딩이 없는 모든 스레드가 공유하는 바인딩이에요. 따라서 Var의 값은 그것의 스레드별 바인딩 값이거나, 값을 요청하는 스레드에 바인딩되어 있지 않다면 (있다면) 루트 바인딩의 값이에요.
특수 폼 def는 Var를 생성(그리고 인턴)해요. Var가 이미 존재하지 않고 초기 값이 제공되지 않으면, var는 바인딩되지 않은(unbound) 상태예요:
user=> (def x)
#'user/x
user=> x
#object[clojure.lang.Var$Unbound 0x14008db3 "Unbound: #'user/x"]
초기 값을 제공하면 루트를 바인딩해요(이미 바인딩되어 있었더라도).
user=> (def x 1)
#'user/x
user=> x
1
기본적으로 Var는 정적(static)이지만, Var를 dynamic으로 표시하면 binding 매크로를 통해 스레드별 바인딩을 허용할 수 있어요. 각 스레드 안에서 그것들은 스택 규율(stack discipline)을 따르지 않아요:
user=> (def ^:dynamic x 1)
user=> (def ^:dynamic y 1)
user=> (+ x y)
2
user=> (binding [x 2 y 3]
(+ x y))
5
user=> (+ x y)
2
binding으로 만든 바인딩은 다른 어떤 스레드에서도 볼 수 없어요. 마찬가지로 binding으로 만든 바인딩은 할당될 수 있는데, 이는 중첩 컨텍스트가 호출 스택에 놓이기 전에 코드와 통신하는 수단을 제공해요. 이 능력은 위 코드 블록처럼 메타데이터 태그 ^:dynamic을 true로 설정함으로써만 선택(opt-in)돼요. 컨텍스트 안에서 정적 Var를 재정의하고 싶은 시나리오가 있을 수 있는데, Clojure는 (1.3부터) 그런 목적을 위해 with-redefs와 with-redefs-fn 함수를 제공해요.
defn으로 정의된 함수는 Var에 저장되어, 실행 중인 프로그램에서 함수의 재정의를 허용해요. 이는 또한 관점 지향(aspect-)·컨텍스트 지향 프로그래밍의 많은 가능성을 활성화해요. 예를 들어 특정 호출 컨텍스트나 스레드에서만 로깅 동작으로 함수를 감쌀 수 있어요.
본문
바인딩 전달 (Binding conveyance)
일부 Clojure 동시성 함수(futures, agents)는 "바인딩 전달(binding conveyance)"을 제공하는데, 현재의 동적 바인딩 집합을 다른 스레드로 전달해 같은 환경으로 비동기적으로 작업을 계속할 수 있게 해 줘요. 이 기능은 future, send, send-off, pmap이 제공해요.
(def ^:dynamic *num* 1)
(binding [*num* 2] (future (println *num*)))
;; prints "2", not "1"
(set! var-symbol expr)
할당 특수 폼이에요.
첫 번째 피연산자가 심볼이면, 그것은 전역 var로 해석되어야 해요. var의 현재 스레드 바인딩 값이 expr의 값으로 설정돼요. 현재 set!을 사용해 var의 루트 바인딩을 설정하려고 시도하는 것은 오류예요. 즉 var 할당은 스레드-지역적이에요. 모든 경우에 expr의 값이 반환돼요.
참고 — 함수 매개변수나 지역 바인딩에는 할당할 수 없어요. Clojure에서 변경 가능한 것은 Java 필드, Var, Ref, Agent뿐이에요.
Java 필드에 set!을 사용하는 것은 Java Interop에 문서화되어 있어요.
인턴 (Interning)
네임스페이스 시스템은 심볼에서 Var 객체로의 전역 맵을 유지해요 (Namespaces 참고). def 표현식이 def되고 있는 심볼에 대해 현재 네임스페이스에서 인턴된 항목을 찾지 못하면 하나를 만들고, 그렇지 않으면 기존 Var를 사용해요. 이 찾거나-만들기(find-or-create) 과정을 인터닝(interning)이라 불러요. 이는 unmap되지 않는 한 Var 객체가 안정적인 참조이고 매번 조회할 필요가 없다는 뜻이에요. 또한 네임스페이스가 전역 환경을 구성하며, Evaluation에서 설명하듯 컴파일러가 모든 자유 심볼을 Var로 해석하려 시도한다는 뜻이기도 해요.
var 특수 폼 또는 pass:[#'] 리더 매크로 (Reader 참고) 를 사용해 현재 값 대신 인턴된 Var 객체를 얻을 수 있어요.
인턴되지 않은 Var (Non-interned Vars)
with-local-vars를 사용해 인턴되지 않은 var를 만들 수 있어요. 이 var들은 자유 심볼 해석 중에 발견되지 않을 것이고, 그 값은 수동으로 접근해야 해요. 하지만 유용한 스레드-지역 변경 가능한 셀(cell) 역할을 할 수 있어요.
Var 메타데이터 (Var metadata)
var를 만드는 폼들(def, defn, defmacro 등)은 표준 var 메타데이터 집합을 사용해 var를 서술해요. 이 폼들 중 일부는 메타데이터에 저장된 값을 받기 위해 명시적 문법을 사용하지만, 일반적으로 그 메타데이터를 var 심볼에 맵으로 제공할 수도 있어요.
(var 정의에서 모두 선택적인) 공통 var 메타데이터 키:
:doc- var를 문서화하는 문자열, 보통 docstring 매개변수로 설정돼요:added- 이 var가 추가된 버전을 문서화하는 문자열:private- 불리언 플래그, 종종defn-으로 설정되며, 작성자가 이 var가 구현 세부 사항이라는 의도를 밝히는 데 사용돼요. private var는 전역적으로 접근 가능하지만, non-private var만 필터링하는ns-... 함수에는 referred되거나 나열되지 않아요.:arglists- arglist 콜렉션, 제공되지 않으면 자동으로 생성되며, 대부분 매크로 문법을 문서화하는 데 사용돼요:macro-defmacro가 자동으로 추가하는 불리언 플래그 (보통 직접 사용되지 않음):tag- var의 값 타입 또는 var에 담긴 함수의 반환 타입에 대한 타입 식별자(보통 클래스). var 메타데이터는 평가되므로, var의^long같은 타입 힌트는long원시 타입 힌트가 아니라long함수로 평가된다는 점에 주의하세요. 일반적으로 defn var에는 arglist에 타입 힌트를 쓰는 것이 선호돼요.:test-clojure.test프레임워크가 이 키를 사용해 var에 유닛 테스트를 붙여요 (보통 직접 사용되지 않음):dynamic- var가 스레드 컨텍스트에서 동적으로 다시 바인딩될 수 있음을 나타내요 (위 참고). dynamic var는 직접 연결(direct linking)으로 컴파일할 때 직접 연결되지 않아요.:redef- var가 직접 연결로 컴파일할 때 직접 연결되지 않아야 함을 나타내요(따라서 재정의를 허용):static- 더 이상 사용되지 않음 (원래 var는 기본적으로 dynamic이었고, 이제는 기본적으로 static임):const- var가 컴파일 시 상수이고 컴파일러가 그 값을 사용하는 코드에 인라인할 수 있음을 나타내요. 참고: 이것은 거의 필요하지 않고 컴파일 시 상수(읽지만 평가되지 않는)에서만 동작해요. 숫자, 문자열 등 (클래스, 함수, ref 타입 등은 아님). const var를 재정의하거나 동적으로 바인딩해도, 이미 컴파일되어 런타임에 로드된 var를 소비하는 코드에는 영향을 주지 않아요.
컴파일 중 직접 연결과 메타 제거(elision)에 대한 자세한 내용은 Compiler Options_compiler_options)도 참고하세요.
관련 함수 (Related functions)
def의 변형: defn defn- definline defmacro defmethod defmulti defonce defstruct- 인턴된 Var 다루기: declare intern binding find-var var
- Var 객체 다루기: with-local-vars var-get var-set alter-var-root var? with-redefs with-redefs-fn
- Var 검증기: set-validator! get-validator
- Var 메타데이터 사용: doc find-doc test