2015년 8월 22일 토요일

Clojure Java Interop - 자바 호환성이 정말 도움되나?

요며칠 Clojure로 작업하면서 몇가지 라이브러리 가져다 쓰는데, 특히 Java 라이브러리르 Clojure 스럽게 래핑해놓은 라이브러리에서 불편함이 있어서 옮겨본다.

Rich Hichey가 Clojure를 소개하면서 JVM 기반으로 언어로 만든 이유는 언어와 달리 성공적인 플랫폼이고 이미 많은 라이브러리들이 나와 있기 때문에 그 이득을 누리기 위해서라고 한 걸 들은 적이 있다. 정확한지는 모르겠지만... 암튼 그래서 쉽게 Java쪽 코드를 불러 쓸 수 있게 해놓았다. 그것이 Java Interop이다.

어차피 대부분의 Java 라이브러리들은 객체 중심으로 작성되어 있는데, 그러다보니 이를 Clojure스럽게 사용할 수 있게끔 포장해놓은 라이브러리들이 있게 된다. Interop 덕분에 그냥 써도 되지만 그러면 Clojure Idiomatic하지 않으니까! 그런데 이런 포장 라이브러리가 전체 API를 다 커버하지 못하는 경우가 있고, 그러면 결국은 다시 원래의 Interop에 기댈 수 밖에 없다. 기껏 포장 라이브러리 써서 좀 Clojure스럽게 짜보려 했지만 그러기 어렵단 얘기다. 물론 대부분은 오픈소스여서 빠진 wrapper는 추가하고 Pull request 보내도 되겠지만 매번 그러기도 쉽지 않다.

예를 들어, 오늘 사용한 라이브러리 두 개는 RxClojureclj-time 이었다.  각각은 RxJavaJoda-Time 을 래핑해놓은 것이다.

RxClojure의 간단한 예를 보면 ...

(require '[rx.lang.clojure.core :as rx])

(->> my-observable
     (rx/map (comp clojure.string/lower-case :first-name))
     (rx/map clojure.string/lower-case)
     (rx/filter #{"bob"})
     (rx/distinct)
     (rx/into []))

상당히 Clojure 스럽게 바뀌어 있다. -> 대신 ->> 쓰레딩을 사용할 수 있게 되었고, namespace rx만 지정해주면 map, filter, into 등을 clojure.core의 것들처럼 사용할 수 있다.

하지만 RxJava의 Observable 클래스에는 백여개 정도의 메쏘드(static포함)가 정의되어 있는데, 이 중 하필 오늘 사용하려한 buffer메쏘드가 RxClojure에 아직 포함되지 않았다.  그래서 rx/map, rx/filter 처럼 rx/buffer 이렇게 쓰지 못하고 결국 (.buffer ...) 로 중간에 형식이 바뀌어야 했다. 이것이 별거 아닐수도 있지만 ->> 를 쓰는 중에 다시 -> 를 쓸 수도 없는 일 아닌가. 그래서 전부 .filter, .map 처럼 바꾸고 -> 를 사용하게 되었다. 그런데 이젠 또 Functional interface인 rx.functions.* 의 인터페이스들과 Clojure의 람다(fn)가 직접 호환되지 않는다는 문제가 드러났다. .map을 rx/map으로 바꾸고 인자의 순서도 바꾸면서 알게 모르게 Clojure의 람다를 사용하도록 친절하게 바꿔놓았는데, .map을 사용하려하니 문제가 된 것이다. RxClojure는 이런 경우를 대비하여 rx.lang.clojure.interop/fn 이라는 편의 매크로를 만들어놨다. Java가 람다식으로부터 해당 함수형 인터페이스로 자동 변환되는 것과는 달라서 불편함이 따른다.

(require '[rx.lang.clojure.interop :as rx])  ;; <- interop 으로 바뀌었다.

(-> my-observable
  (.map (rx/fn [x] ...))
  (.buffer 1 TimeUnit.MINUTE)
  (.subscribe (rx/action [xs] ... )))

clj-time 의 경우도 LocalDateTime과 DateTime의 자동변환이 이뤄지지 않기 때문에 비교문에서 클래스캐스팅 예외가 나오는데, 이를 확인하려면 Joda-Time의 어떤 메쏘드와 clj-time의 어떤 함수가 매핑되는지 눈대중으로 찍으면서 타입을 확인 했다. 그런 다음엔 다시 Joda-Time에서 적당한 변환 메쏘드를 찾고, 이는 다시 Java Interop을 거쳐서 변환했다. 런타임에 타입이 문제된다는 걸 확인하고서도 이를 바로잡기 위해 불편한 과정을 밟아야 한다는 점이 매우 아쉬웠다.




댓글 없음:

댓글 쓰기