On this page:
4.1 Var와 Binding
4.2 Symbol의 평가와 Var의 평가
4.2.1 var가 가리키는 값이 함수가 아닌 경우
4.2.2 var가 가리키는 값이 함수인 경우

4 Vars

4.1 Var와 Binding

다음은 var의 binding에 대한 설명이다.

Clojure에서 def로 선언된 변수명이나 defn으로 선언된 함수명은 해당 namespace의 심볼 테이블에 symbol이 var를 가리키는 형태(symbol –> var)로 등록되고 관리된다. 그리고 프로그램 실행 중에 이와 관련된 정보를 참조할 수도 있다.

그런데 var의 binding에는 크게 두 종류가 있다. 바로 root binding과 thread-local binding이 그것이다.

root binding은 모든 쓰레드에서 접근 가능한 binding인 반면에, thread-local binding은 이름에서 짐작할 수 있듯이 해당 쓰레드에서만 접근 가능하다.

다음의 예는 root binding의 예이다.

(def a 10)
(def b 20)

;; 이 함수가 실행되고 있는 main thread에서도 접근 가능
(+ a b)
; => 30

;; future에 의해 실행되는 별도의 쓰레드에서도 접근 가능
@(future (+ a b))
; => 30

이에 반해 thread-local binding을 이용하려면, 다음 두 가지 조건을 모두 충족시켜 주어야 한다.

  1. def 선언시 ^:dynamic을 반드시 포함해 주어야 한다.

  2. ’binding 매크로 안’에서만 dynamic var를 참조해야 한다.

(def ^:dynamic c 10)
(def ^:dynamic d 20)

;; 이 때는 dynamic으로 선언되어 있어도 binding 매크로 안에서
;; 참조가 이루어지고 있지는 않으므로 여전히 root binding이다.
(+ c d)
; => 30

;; future에 의해 실행되는 별도의 쓰레드에서 접근하지만
;; binding 매크로 안에서 참조가 이루어지고 있지는 않으므로
;; 여전히 root binding이다.
@(future (+ c d))
; => 30

;; 위의 두 가지 조건을 모두 만족시키므로 thread-local binding이다.
;; + 함수가 참조하고 있는 c와 d의 삾은, 이 함수가 실행되고 있는
;; main thread에서만 접근 가능. 다른 쓰레드는 이 100과 200의 값을
;; 볼 수 없다.
(binding [c 100 d 200]
  (+ c d))
; => 300

;; 위의 두 가지 조건을 모두 만족시키므로 thread-local binding이다.
;; + 함수가 참조하고 있는 c와 d의 값은, 이 함수가 실행되고 있는
;; future에 의해 실행되는 별도의 쓰레드 안에서만 접근 가능하다.
@(future
   (binding [c 300 d 400]
     (+ c d)))
; => 700

;; thread-local binding에서 binding된 값들은
;; root binding의 값에는 영향을 미치지 못한다.
(+ c d)
; => 30

4.2 Symbol의 평가와 Var의 평가

Clojure에서는 top-level symbol(즉, def로 정의되는 심볼)이, Common Lisp에서와는 달리, 값(value)을 직접 가리키지 않고, var를 경유해 가리킵니다. 이 글에서는 그 작동 메카니즘을 제가 이해한 대로 설명해 보겠습니다.

symbol –> var –> value

인터넷을 검색해 봐도 symbol의 평가와 var의 평가에 대해 그 차이를 명쾌하게 설명한 글을 찾기 어려워, 제가 직접 테스트한 결과를 바탕으로 제 나름대로 분석한 내용입니다. 제가 이해한 바가 실제와 다를 수도 있음을 참고하시고, 잘못된 이해라고 판단되는 부분이 있으면 지적해 주시기 바랍니다. 아울러 이 글은 Clojure의 symbol이나 var에 대한 기본적인 이해가 선행되어야 제대로 이해할 수 있는데, 그 부분에 대한 기초적인 설명까지 덧붙이자면 글이 너무 길어질 것 같아서 그에 대한 설명을 다음 기회로 미루는 것을 미리 양해해 주시기 바라며, 따라서 이 글이 지금 당장 이해 안된다고 너무 자책하지 마시길 바랍니다. :)

4.2.1 var가 가리키는 값이 함수가 아닌 경우

; 이름공간를 ns-a로 변경한다.
user> (ns ns-a)
nil

; prompt가 ns-a로 변경된 것을 확인할 수 있다.
; ns-a 이름공간에서 심볼 a를 정의한다.
;
;  심볼   var          value
; —
;  a –>  #’ns-a/a –> 10
ns-a> (def a 10)
#’ns-a/a

; top-level 심볼 a를 평가하면, 심볼 a가 가리키고 있는 var인
; #’ns-a/a를 거쳐 value를 가져오게 된다.
ns-a> a
10

; 이름공간을 ns-b로 변경한다.
ns-a> (ns ns-b)
nil

; prompt가 ns-b로 바뀐 것을 확인할 수 있다.
; ns-b 이름공간에서 심볼 a1을, ns-a/a로 정의한다.
;
; 심볼    var           value
; —-
;  a1 –> #’ns-b/a1 –> 10
ns-b> (def a1 ns-a/a)
#’ns-b/a1

; a1을 평가하면 예상한 결과값이 나온다.
; top-level 심볼 a1를 평가하면, 심볼 a1이 가리키고 있는 var인
; #’ns-b/a1를 거쳐 value 10을 가져오게 된다.
ns-b> a1
10

; ns-b 이름공간에서 심볼 a2를 #’ns-a/a로 정의한다 (#’가 붙어있음에 주의).
;
; 심볼    var           var          value
; —
;  a2 –> #’ns-b/a2 –> #’ns-a/a –> 10
ns-b> (def a2 #’ns-a/a)
#’ns-b/a2

; a2를 평가하면 #’ns-a/a가 나온댜.
ns-b> a2
#’ns-a/a

; @a2를 평가하면 10이 나온댜.
ns-b> @a2
10

; ns-a 이름공간으로 돌아간다.
ns-b> (ns ns-a)
nil

; ns-a 이름공간에서 심볼 a를 ’재정의’한다.
;
;  심볼   var          value
; —
;  a –>  #’ns-a/a –> 100
ns-a> (def a 100)
#’ns-a/a

; ns-b 이름공간으로 되돌아간다.
ns-a> (ns ns-b)
nil

; a1을 평가하면, 예전에 정의한 값 10이 반환된다.
;
; 심볼    var           value
; —-
;  a1 –> #’ns-b/a1 –> 10
ns-b> a1
10

; a2를 평가하면, #’ns-a/a가 반환된다.
;
; 심볼       var        var          value
; —
;  a2 –> #’ns-b/a2 –> #’ns-a/a –> 100
ns-b> a2
#’ns-a/a

; @a2를 평가하면, 새로 정의한 값 100이 반환된다.
ns-b> @a2
100

4.2.2 var가 가리키는 값이 함수인 경우

; 이름공간를 ns-a로 변경한다.
user> (ns ns-a)
nil

; prompt가 ns-a로 변경된 것을 확인할 수 있다.
; ns-a 이름공간에서 심볼 f를 함수로 정의한다.
;
;  심볼   var          value
; —-
;  f –>  #’ns-a/f –> 함수 객체: (+ a b)
ns-a> (defn f [a b] (+ a b))
#’ns-a/f

; 이름공간을 ns-b로 변경한다.
ns-a> (ns ns-b)
nil

; prompt가 ns-b로 바뀐 것을 확인할 수 있다.
; ns-b 이름공간에서 f1을 ns-a/f로 정의한다.
;
; 심볼    var           value
; —-
;  f1 –> #’ns-b/f1 –> 함수 객체: (+ a b)
ns-b> (def f1 ns-a/f)
#’ns-b/f1

; f1을 함수로 실행하면 예상한 결과값이 나온다.
ns-b> (f1 10 20)
30


; ns-b 이름공간에서 f2를 #’ns-a/f로 정의한다.
;
; 심볼    var           var          value
; —–
;  f2 –> #’ns-b/f2 –> #’ns-a/f –> 함수 객체: (+ a b)
ns-b> (def f2 #’ns-a/f)
#’ns-b/f2

; f2을 함수로 실행하면 예상한 결과값이 나온다.
; 참고로, Clojure에서는 var도 IFn 인터페이스가 구현되어 있어,
; 함수 자리에 곧바로 올 수 있다.
ns-b> (f2 10 20)
30

; 물론 @f2로 호출도 가능하다.
ns-b> (@f2 10 20)
30


; ns-a 이름공간으로 돌아간다.
ns-b> (ns ns-a)
nil

; ns-a 이름공간의 함수 f를 ’재정의’한다.
;
;  심볼   var          value
; —-
;  f –>  #’ns-a/f –> 함수 객체: (* a b)
ns-a> (defn f [a b] (* a b))
#’ns-a/f


; ns-b 이름공간으로 되돌아간다.
ns-a> (ns ns-b)
nil

; f1을 함수로 실행하면, 예전에 정의한 함수 객체 (+ a b)가 실행된다.
;
; 심볼    var           value
; —-
;  f1 –> #’ns-b/f1 –> 함수 객체: (+ a b)
ns-b> (f1 10 20)
30

; f2를 평가하면, 새로 정의한 함수 객체 (* a b)가 실행된다.
;
; 심볼       var        var          value
; —–
;  f2 –> #’ns-b/f2 –> #’ns-a/f –> 함수 객체: (* a b)
ns-b> (f2 10 20)
200