Clojure의 모든 변수는 immutable 변수이다.(하긴 바뀌지 않으면 변수라고 할 수 없겠다.) 물론 몇몇 언어에서도
불변 변수를 쓰기는 하지만 Clojure의 그것은 Erlang의 그것과 유사하다. 왜냐면 같은 변수명에 값을 새로 assign하는것도
기본적으로는(?) 불가능하기 때문이다. ㅋㅋ
아무래도 concurrency를 지향하기 때문에 다분히 그런거라 생각했지만 영 몇몇 테스트 코딩하는데 불편함이 있어서 자바 객체를 만들어서 그곳에 저장하곤 했다.
그러니까 함수내 로컬 변수로 vector를 만들어 (conj vector “123”)를 아무리 쳐봐야 매번 새로운 객체만 생성할 뿐이고 이것을 기존 변수로 set! 하려면 에러만 뜬다.
물론 이런걸 피하기 위한 방법들이 recursive한 함수를 만드는 방법이 프로그래밍적인 방법으로 생각할수 있고 이런 방법은 Erlang에서도 자주 쓰이는 방법이다.
Erlang
이 concurrency한 프로그램을 만들기 위한 많은 문법적인 요소에 제한을 가한 단점이 있는데, Clojure같은 경우는
Concurrency를 유지하는것을 기본으로 하면서 다양한 방법들에 대한 가능성과 제한을 동시에 제안하고 있다.
일
단 (def a 1) 같은 문으로 변수 정의를 하면 이것은 무조건 Global Variable이 된다 그러나 이것을 함수 안이나
바깥에서 변형을 하고자 접근을 하면 절대 변형이 안된다. 왜냐면 이 변수는 root 바인딩이 되어 있는 함수기 때문이다. 일단
root 바인딩이 되어 있는 변수의 값은 절대 어떤 스레드도 바꿀 수 없다. 물론 함수 안에서 바인딩을 그 해당 스레드
바인딩으로 바꿀 수 있는 binding 같은 함수와 let과 같은 문이 있지만. 이렇게 변형된 변수는 전역변수와는 이름만 같을
뿐 전혀 공유하는 바가 없다.
예를들어 보자
(def b “b”) ;b 변수 “b”로 정의
(set! b (str b “c”)) ; b 변수 변경 “bc”로 변경하고자 함
이런 식의 코드를 짜면 “Can’t change/establish root binding of: b with set” 이런 에러가 뜬다. 뭐 root 바인딩 된것을 바꿀 수 없다는 것이다.
이런점을 해결하기 위해 Clojure는 ref라
는 것을 지원한다. 그러니까 C++의 reference같은 개념을 지원하는것이다. 하지만 reference가 편하긴 하지만
다수의 스레드를 운영할 시 문제가 생길 수 있으니 Clojure도 concurrency를 지원하니 뭔가 보호대책을 제안해 줄
것이다.
일단 b를 reference 변수로 정의 한다.
(def b (ref “b”))
그냥 “b”를 쳐보면 아래와 비슷한 코드가 뜬다. 아무래도 주소 정보겠다.
clojure.lang.Ref@f5b2e4
b 변수를 1 증가 시키기 위해서는 단 하나의 트랜젝션만 접근이 가능한 (dosync )라는 매크로로 감싸줘야 한다. 만일 감싸주지 않으면 무수한 에러가 뜰 것이다. ^^;
(dosync (ref-set b (str @b “c”)))
위 코드는 b의 값과 “c”를 결합해 string을 만들고 이의 reference를 b에 저장하라는 코드이다.
다시 b를 쳐보면 주소에는 변함이 없다.
clojure.lang.Ref@f5b2e4
그렇지만 ‘@b’로 reference 값을 보면 “bc”로 바뀌어 있는것을 볼 수 있다.
C++처럼 reference를 지원하면서 이를 write하기 위해서는 자바에서 synchronization 으로 클래스를
감싸주는 것처럼 하나의 트랜젝션만을 허용하기 위해 dosync를 이용하고 있었다. 흥미롭지만 역시 이런 방법은 역시 다중 스레드
방식의 프로그래밍에서 단점으로 작용하는 병목현상을 야기할 수 있겠다.
위 방법을 Java API를 이용해서 해결해 보자면
(def b (new StringBuffer “b”))
(dosync (. b append “c”))
로 할 수 있지만 (dosync)를 하지 않아도 에러가 뜨지 않는다.
문법적으로 해결하던 reference로 해결하던 Java API로 해결하던 선택해서 쓰면 된다. 하지만 역시 concurrency를 대비한 언어처럼 편리함과 함께 뭔다 다른 강제기능을 함께제공한다는것이 이채롭다.
그래서 결론은 무분별한 Java API 사용을 최대한 자제하고 자체로 제공되는 라이브러리와 ref 같은것 그리고 Lisp 특유의 Recursive정도를 사용해서 코딩한다면 멀티 스레드로 인해서 나중에 머리아플 일은 없을거란 생각을 해본다. 물론 Java API를 쓴다면 dosync 메크로를 쓰는걸 잘 고민하고 써야할 것인데.. 빼먹지 않아야 될 곳에 빼지말고 넣지 말아야 할 곳에 넣지 않아야겠다.
물론 그런 고민하기 싫으면 그냥 언어에서 제공하는 것들을 그냥 쓰면 알아서 체크해준다.
Clojure의 ref by from __future__ import dream is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.