리스트 캄프리헨션

리스트 캄프리헨션은 기존의 리스트에 기반한 리스트를 만들기 위해 일부 프로그래밍 언어에서 사용 가능한 문법적 구조다. 덕분에 map과 filter 함수의 용도와는 다르게 수학적인 집합 작성 표기법 (집합 캄프리헨션)의 형태를 따른다.

역사

SETL 프로그래밍 언어 (1969)는 리스트 캄프리헨션과 유사한 집합 형성 구조를 가지고 있다. 이 코드는 2부터 N까지의 모든 소수(prime number)를 출력한다:
   print([n in [2..N] | forall m in {2..n - 1} | n mod m > 0]);
컴퓨터 알게브라 시스템 AXIOM (1973)은 스트림을 처리하는 유사한 구조를 가지고 있지만, 그 구조를 위한 "캄프리헨션"이라는 용어는 1977년 Rod Burstall과 John Darlington의 프로그래밍 언어인 NPL의 명세서에서 처음 사용되었다.
리스트 캄프리헨션을 구성하는 스몰토크 블록 컨텍스트 메시지는 적어도 스몰토크-80 이후부터 해당 언어에 탑재되기 시작했다.
Burstall과 Darlington의 NPL 연구는 1980대에 많은 프로그래밍 언어에 영향을 주었지만, 모두가 리스트 캄프리헨션을 포함한 것은 아니었다. 1985년에 발표된, 영향력 있는 순수 함수형 언어인 Miranda는 예외였다. 이후에 개발된 표준 순수 함수형 언어 Haskell은 Miranda의 수많은 장점은 물론, 리스트 캄프리헨션도 포함한다.
캄프리헨션은 데이터베이스를 위한 쿼리 표기법으로 제안되었으며 Kleisli   데이터베이스 쿼리 언어로 구현되었다.

모나드 캄프리헨션

Haskell에서, 모나드 캄프리헨션은 함수형 프로그래밍의 다른 모나드에 대한 리스트 캄프리헨션의 일반화다.

집합 캄프리헨션

Python 언어 버전 3.x와 2.7은 집합 캄프리헨션을 위한 문법을 도입했다. 리스트 캄프리헨션의 형태와 유사하게, 집합 캄프리헨션은 리스트 대신 Python 집합을 생성한다.
>>> s = {v for v in 'ABCDABCD' if v not in 'CB'}>>> print(s){'A', 'D'}>>> type(s)<class 'set'>>>>
Racket 집합 캄프리헨션은 리스트 대신 Racket 집합을 생성한다.
(for/set ([v "ABCDABCD"] #:unless (member v (string->list "CB")))
         v))

딕셔너리 캄프리헨션

Python 언어 버전 3.x와 2.7에서는 리스트 캄프리헨션의 형태와 유사하지만, 리스트 대신에 Python dicts를 생성하는, 딕셔너리 캄프리헨션을 위한 새로운 문법을 도입했다.
>>> s = {key: val for key, val in enumerate('ABCD') if val not in 'CB'}>>> s{0: 'A', 3: 'D'}>>>
Racket 해시 테이블 캄프리헨션은 Racket 해시 테이블 (Racket 딕셔너리 타입 구현 중 하나)을 생성한다.
(for/hash ([(val key) (in-indexed "ABCD")]
           #:unless (member val (string->list "CB")))
  (values key val))

병렬 리스트 캄프리헨션

Glasgow Haskell 컴파일러는 병렬 리스트 캄프리헨션 (또는 zip-캄프리헨션이라고도 알려져 있는) 한 가지 예외를 갖고 있는데, 리스트 캄프리헨션 내에 다양하고 독립적인 수식어 분기가 가능하다. 반면, 콤마로 구분되는 수식어는 종속적("내재된")이며, 파이프로 구분된 수식어 분기는 병렬로 수행된다(이는 어떠한 형태의 멀티스레드 방식도 의미하지 않는다: 단지 분기가 매핑된다는 것을 의미할 뿐이다).
-- regular list comprehensiona = [(x,y) | x <- [1..5], y <- [3..5]]-- [(1,3),(1,4),(1,5),(2,3),(2,4) ...-- zipped list comprehensionb = [(x,y) | (x,y) <- zip [1..5] [3..5]]-- [(1,3),(2,4),(3,5)]-- parallel list comprehensionc = [(x,y) | x <- [1..5] | y <- [3..5]]-- [(1,3),(2,4),(3,5)]
Racket의 캄프리헨션 표준 라이브러리는 캄프리헨션에 대한 병렬과 내재된 버전을 포함하는데, 이름에서 "for"와 "for*"로 구분된다. 예를 들어, 벡터 캄프리헨션인 "for/vector"와 "for*/vector"는 각각 시퀀스에 대한 병렬 및 내재된 이터레이션을 생성한다. 다음은 Haskell 리스트 캄프리헨션 예제에 대한 Racket 코드다.
> (for*/list ([x (in-range 1 6)] [y (in-range 3 6)]) (list x y))'((1 3) (1 4) (1 5) (2 3) (2 4) (2 5) (3 3) (3 4) (3 5) (4 3) (4 4) (4 5) (5 3) (5 4) (5 5))> (for/list ([x (in-range 1 6)] [y (in-range 3 6)]) (list x y))'((1 3) (2 4) (3 5))
Python에서는 다음과 같이 할 수 있다:
# regular list comprehension>>> a = [(x, y) for x in range(1, 6) for y in range(3, 6)][(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), ...# parallel/zipped list comprehension>>> b = [x for x in zip(range(1, 6), range(3, 6))][(1, 3), (2, 4), (3, 5)]

XQuery와 XPath

NPL의 본래 용도와 마찬가지로, 이들은 원래 데이터베이스 액세스 언어다.
이것이 캄프리헨션 컨셉을 좀 더 중요하게 만드는데, 전체 목록을 검색하고 그것에 대해 연산을 수행하는 것이 계산 상으로는 불가능하기 때문이다(초기 '전체 목록'은 전체 XML 데이터베이스일 수도 있다).

XPath의 표현은 다음과 같다:
 /library/book//paragraph[@style='first-in-chapter']
이는 개념적으로 연속된 "단계"들로 계산되는데, 여기서 각 단계는 리스트를 만들어내며 다음 단계는 이전 단계의 출력에 대한 각각의 요소에 필터 함수를 적용한다.
XQuery에서, 전체 XPath를 사용할 수 있지만, FLWOR 구문 또한 사용 가능한데, 이는 좀 더 강력한 캄프리헨션 구조다.
 for $b in //book
 where $b[@pages < 400]
 order by $b//title
 return
   <shortBook>     <title>{$b//title}</title>     <firstPara>{($book//paragraph)[1]}</firstPara>   </shortBook>
여기서 XPath인 //book가 수행될 경우 (리스트라고도 하는) 하나의 시퀀스가 생성된다; where문은 함수적인 "필터"이고, order by문은 결과를 정렬하며,  <shortBook>...</shortBook> XML 스니펫은 실제로 다른 함수형 언어에서 볼 수 있는 'map'을 사용해 시퀀스 내 각각의 요소에 대해 XML을 빌드하거나 변형하는 익명 함수다.
그래서, 위에서 본 FLWOR 구문은 다른 함수형 언에서 다음과 같이 구현될 수 있다:
 map(
   newXML(shortBook, newXML(title, $1.title), newXML(firstPara, $1...))
   filter(
     lt($1.pages, 400),
     xpath(//book)
   )
 )