Clojure에 포함된 라이브러리들

Clojure에 포함된 라이브러리들

클로저는 작은 언어지만, 그 안에 꽤 쓸모 있는 라이브러리들을 몇 개 들고 있어요. 이 문서에서는 Clojure가 기본으로 제공하는 네임스페이스들이 어떤 일을 하는지 하나씩 살펴볼게요. 대부분은 핵심 언어가 아니라 '기본 포함 라이브러리'로 제공되는 것들이라, 필요할 때 골라 쓰면 됩니다.

출처: Clojure 공식문서

본문

Java 유틸리티 (clojure.java.*)

clojure.java.basis 클래스패스에 현재 로드된 라이브러리와 버전 정보를 담고 있는 초기 및 현재 런타임 basis에 접근할 수 있게 해줘요.

clojure.java.io Java 클래스 위에 구현된 다형성(polymorphic) I/O 유틸리티 함수들을 담고 있습니다. 파일, 스트림, 리더 같은 것들을 Clojure에서 자연스럽게 다룰 때 사용해요.

clojure.java.javadocs javadoc 함수를 제공하는데, 클래스나 인스턴스 클래스에 맞는 적절한 Javadocs를 열어 보여주려고 시도합니다.

clojure.java.process 서브프로세스를 실행하고 통신하는 것을 지원해요. 이 네임스페이스는 clojure.java.shell를 대체하며, 최신 Java 프로세스 API를 사용합니다.

clojure.java.shell sh 함수를 제공해서 서브프로세스를 실행하고 관리할 수 있게 해줍니다. 예상되는 인자에 대한 자세한 내용은 함수 문서를 참고하세요.

병렬 처리 (DEPRECATED)

병렬 라이브러리(네임스페이스 parallel, 파일 parallel.clj)는 ForkJoin 라이브러리를 감싼 것입니다. 이 라이브러리는 이제 더 이상 권장되지 않아요(deprecated).

기본 아이디어는 이렇습니다. Clojure 컬렉션, 특히 벡터를 par 함수로 병렬 배열(parallel array)로 바꿔서 사용합니다. 대부분의 함수는 컬렉션을 받아 필요한 경우 자동으로 par를 호출하므로, 보통은 bound/filter/map 연산을 붙일 때만 명시적으로 par를 호출하면 돼요.

병렬 배열은 실제 계산 전에 bound(경계)와 filter(필터), mapping(매핑) 함수를 붙여둘 수 있어요. 계산은 배열에 대한 여러 연산(pvec/psort/pfilter-nils/pfilter-dupes) 중 하나가 실행되는 시점에 일어납니다. 시퀀스로 복합 연산을 단계별로 처리하는 대신, 여기서는 par에 연산을 넘겨 bound/filter/mapping을 붙여서 합성해요. 주의할 점은 붙이는 순서에 의미가 있다는 것입니다. bound가 먼저, 그다음 filter, 마지막으로 mapping 순서예요. 모든 연산은 배열이 실현될 때나 preduce/pmin/pmax 같은 집계 연산을 수행할 때 fork-join이 지원하는 정교한 work-stealing 시스템과 여러 스레드를 이용해 병렬로 일어납니다.

병렬 배열은 pvec로 Clojure 벡터로 실현할 수 있어요.

(load-file "src/parallel.clj")
(refer 'parallel)

(def f (vec (take 20 (repeatedly #(rand-int 20)))))

f
-> [11 7 10 9 4 1 4 18 15 13 10 7 0 9 16 6 19 11 14 7]

;값/인덱스 쌍 중 값이 자신의 인덱스보다 작은 것들을 병렬로 반환

(pvec (par f :filter-index < :map-index vector))
-> [[1 5] [4 6] [7 11] [0 12] [9 13] [6 15] [11 17] [14 18] [7 19]]

리플렉션 유틸리티 (clojure.reflect)

호스트 타입의 리플렉션 정보를 모으고 만들 때 유용한 함수와 프로토콜을 제공합니다. 결과를 Clojure 데이터로 만들어주죠.

REPL 유틸리티 (clojure.repl)

REPL에서 대화형으로 쓰기 좋게 만들어진 유틸리티 모음이에요.

집합과 관계대수 (clojure.set)

관계대수(relational algebra)를 이용해 수학적 집합을 다루고, 만들고, 조회하는 데 유용한 함수들을 담고 있어요.

문자열 처리 (clojure.string)

문자열을 다루는 함수들입니다. split, join, replace 같은 흔한 작업을 할 때 사용해요.

단위 테스트 (clojure.test)

Clojure의 단위 테스트 프레임워크입니다. deftest로 테스트를 정의하고 is로 검증하는 방식이에요.

데이터 구조 순회 (clojure.walk)

중첩된 데이터 구조를 순회하고 조작하는 유틸리티를 제공합니다. 전체 데이터를 바꾸거나 특정 요소를 찾아 바꿀 때 유용해요.

XML (clojure.xml)

XML 데이터를 읽고 쓰는 유틸리티입니다.

Zipper — 함수형 트리 편집 (clojure.zip)

Clojure는 zipper라는 기법을 사용해 순수 함수형이면서도 제네릭한 트리 순회와 편집을 지원합니다(네임스페이스 zip). 배경 지식이 궁금하면 Huet의 functional zipper 논문을 참고해 보세요.

zipper는 계층적 데이터 구조 안의 위치(location) 와 그 위치까지 온 경로를 표현하는 데이터 구조입니다. down/up/left/right로 이동할 수 있고, 노드를 국소적으로 함수형 '편집'(삽입·제거 포함)할 수 있어요. 이렇게 하면 마치 명령형으로 트리를 파괴적으로 순회하는 것처럼 보이는 코드를 짤 수 있습니다. 그리고 작업이 끝나면 root를 호출해 모든 변경이 반영된 새 트리를 받아요. 실제로는 아무것도 변형되지 않으니 스레드 안전하고 공유해도 안전합니다. next 함수는 깊이 우선(depth-first) 순회를 하므로, 다음과 같이 이해하기 쉬운 반복문을 만들 수 있어요.

(def data '[[a * b] + [c * d]])
(def dz (zip/vector-zip data))

;두 번째 * 를 찾기
(-> dz zip/down zip/right zip/right zip/down zip/right zip/node)
-> *

;'remove' 첫 2개 항
(-> dz zip/next zip/remove zip/next zip/remove zip/root)
-> <<c * d#,c * d>>


;'replace' * 을 / 로
(loop [loc dz]
  (if (zip/end? loc)
    (zip/root loc)
    (recur
     (zip/next
      (if (= (zip/node loc) '*)
        (zip/replace loc '/)
        loc)))))

-> [[a / b] + [c / d]]


;'remove' *
(loop [loc dz]
  (if (zip/end? loc)
    (zip/root loc)
    (recur
     (zip/next
      (if (= (zip/node loc) '*)
        (zip/remove loc)
        loc)))))

-> [[a b] + [c d]]


;원본은 그대로
(zip/root dz)
-> [[a * b] + [c * d]]

zipper 생성자는 중첩 시퀀스(nested seqs), 중첩 벡터, 그리고 xml/parse가 만들어내는 XML 요소를 위해 제공됩니다. 다른 데이터 구조를 지원하려면 4~5줄짜리 함수를 직접 작성하면 됩니다.

더 알아보기