시퀀스 (Sequences)

시퀀스 (Sequences)

Clojure는 많은 알고리즘을 시퀀스(seq) 단위로 정의해요. seq는 논리적 리스트로, 구체적인 2-슬롯 구조 대신 ISeq 인터페이스를 통해 다양한 데이터 구조가 자신의 요소를 시퀀스로 노출하게 해줘요. 공식 Reference 기준으로 시퀀스의 개념과 주요 함수를 정리할게요.

출처: Clojure Reference - Sequences

본문

대부분의 리스프에서 리스트는 구체적인 2-슬롯 구조로 표현되지만, Clojure는 ISeq 인터페이스를 사용해요. seq 함수는 컬렉션에 맞는 ISeq 구현을 돌려줘요. seq는 이터레이터와 달리 영속적이고 불변해요 — 컬렉션 안의 상태를 가리키는 커서가 아니라 값 그 자체예요. 그래서 단순 foreach 이상으로 쓸모가 많아요. 함수가 seq를 소비하고 만들 수 있고, 스레드 안전하며, 구조를 공유할 수 있어요.

시퀀스 라이브러리 함수 대부분은 지연(lazy) 이에요. 즉 seq를 돌려주는 함수는 소비되는 만큼 점진적으로 계산되고, seq 인자도 마찬가지로 점진적으로 소비돼요. 지연 seq를 돌려주는 함수는 lazy-seq 매크로로 구현할 수 있어요 (lazy도 참고).

seqIterable 구현 객체에 쓰면 결과 시퀀스는 여전히 불변·영속이고, 데이터를 한 번 지나가는 단일 패스를 나타내요. 그 패스가 지연일 수 있으므로 seq 호출 이후에 생긴 변경도 보일 수 있어요. 또 백킹 이터레이터가 ConcurrentModificationException에 취약하다면 결과 seq도 그럴 수 있어요. 네이티브 Java 배열에 seq를 쓰면 배열 변경이 seq에 그대로 반영되므로, 완전한 불변성을 얻으려면 원본 배열을 복사해야 해요. 그럼에도 seq는 멀티패스·지연 알고리즘을 지원해 유용성이 크기 때문에, seq가 걸린 배열이나 Iterable을 변형하는 코드는 피해야 해요.

시퀀스 라이브러리의 많은 함수는 하나 이상의 컬렉션을 받아 그 위에 seq를 호출한 뒤 결과 seq를 연산해요. 즉 컬렉션을 받지만 실제로는 그 seq를 다루는 거예요.

Seq 인터페이스

(first coll)

컬렉션의 첫 번째 항목을 돌려줘요. 인자에 seq를 호출하고, collnil이면 nil을 돌려줘요.

(rest coll)

첫 항목을 뺀 나머지 항목들의 시퀀스를 돌려줘요. 인자에 seq를 호출하고, 남은 항목이 없다면 seqnil을 돌려주는 논리적 시퀀스를 돌려줘요.

(cons item seq)

item을 첫 요소로, seq를 나머지로 하는 새 seq를 돌려줘요.

rest vs nextlazy-seq에 대한 논의는 lazy를 참고해요.

Seq 라이브러리

여기서는 주요 시퀀스 함수를 능력별로 묶어 소개해요. 어떤 함수는 여러 방식으로 쓰일 수 있어 여러 그룹에 나타날 수 있고, 더 많은 함수는 API 섹션에 있어요.

Clojure 1.7부터는 컬렉션 변형의 대안 모델인 transducer도 제공돼요. transducer는 입력·처리·출력을 분리해 core.async 채널 같은 더 넓은 맥락에서 변형을 재사용하게 해줘요. 아래 리스트의 많은 시퀀스 함수는 입력 컬렉션을 생략하면 transducer를 만들어요. 자세한 내용은 Transducers 페이지를 참고하세요.

짧은 seq → 더 짧은 seq: distinct filter remove for keep keep-indexed 짧은 seq → 더 긴 seq: cons concat lazy-cat mapcat cycle interleave interpose 머리 항목이 빠진 seq: rest next fnext nnext drop drop-while nthnext for 꼬리 항목이 빠진 seq: take take-nth take-while butlast drop-last for seq 재배열: flatten reverse sort sort-by shuffle 중첩 seq 만들기: split-at splitv-at split-with partition partition-all partition-by partitionv partitionv-all 각 항목을 처리해 새 seq 만들기: map pmap mapcat for replace reductions map-indexed seque

seq 사용하기

  • 컬렉션이 seq를 만들 수 있는지 확인: seqable?
  • 특정 번호 항목 추출: first ffirst nfirst second nth when-first last rand-nth
  • seq로 컬렉션 만들기: zipmap into reduce set vec into-array to-array-2d frequencies group-by
  • seq의 항목들을 함수 인자로 넘기기: apply
  • seq에서 불리언 계산: not-empty some reduce seq? every? not-every? not-any? empty?
  • 술어로 seq 검색: some filter
  • 지연 seq 강제 평가: doseq dorun doall
  • 지연 seq가 강제로 평가됐는지 확인: realized?

seq 만들기

  • 컬렉션에서 지연 seq: seq vals keys rseq subseq rsubseq
  • 생성 함수에서 지연 seq: lazy-seq repeatedly iterate
  • 상수에서 지연 seq: repeat range
  • 다른 객체에서 지연 seq: line-seq resultset-seq re-seq tree-seq file-seq xml-seq iterator-seq enumeration-seq

더 알아보기