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

== 개요 ==
{{정리 필요 문단|날짜=2022-07-23}}
다음 예제에서 [[조건제시법|집합 작성 표기법]]의 다음과 같은 예제를 보자.
: <math />
이것은 "<math />는 <math /> <math /> [[자연수]] (<math />) 집합의 요소이다"라고 읽을 수 있다.

가장 작은 자연수, x = 1일 때, x<sup>2</sup>>3 라는 조건을 만족시키지 못하므로 (1<sup>2</sup>>3는 거짓) 2·1은 S에 포함되지 않는다. 다음 자연수, 2는 다른 모든 자연수가 그러하듯 조건을 만족한다 (2<sup>2</sup>>3). 따라서 S는 2·2, 2·3, 2·4...로 구성되며,  S= 4, 6, 8, 10,...이므로 2보다 큰 모든 짝수를 나타낸다.

예제에 대한 주석을 보면:
: <math />
* <math />
* <math />
* <math /> 은 입력 집합 멤버에 대한 필터로 동작하는 술어 표현식이다.
* <math /> 는 술어 표현식을 만족하는 입력 집합의 멤버로부터 새로운 집합의 멤버를 만들어내는 출력 표현식이다.
* <math /> 중괄호는 결과가 집합임을 가리킨다.
* <math /> 수직 바와 콤마는 구분 기호다.
리스트 캄프리헨션은 입력 [[리스트 (컴퓨팅)|리스트]] 또는 [[반복자|반복자(iterator)]]의 순서로 리스트를 생성하는 것을 나타내는 구문 상의 구성 요소와 같다:
* 입력 리스트 멤버를 나타내는 변수.
* 입력 리스트(또는 반복자).
* 술어 표현식(옵션).
* 그리고 술어를 만족시키는 반복 가능한 입력 멤버로부터 출력 리스트의 멤버를 만들어내는 출력 표현식.
출력 리스트 멤버의 생성 순서는 입력의 항목 순서에 기반한다.

이와 유사하게 [[하스켈]]의 리스트 이해 구문에서는, 이러한 집합 빌더 구성을 다음과 같이 작성한다:<syntaxhighlight />여기서,리스트 <code>[0..]</code>는 <math />, <code>x^2>3</code>는 술어를, <code>2*x</code>는 출력 표현식을 나타낸다.

리스트 캄프리헨션은 (집합 멤버와는 달리) 정의된 순서로 결과를 낸다; 그리고 리스트 캄프리헨션은 리스트 전체를 생성하기 보다는, 예를 들어, 무한 리스트 에 대한 예전 하스켈 정의를 허용하기 위해 리스트 멤버를 순서대로 생성할 수도 있다.

== 역사 ==
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]]는 예외였다. 이후에 개발된 표준 순수 함수형 언어 [[하스켈]]은 Miranda의 수많은 장점은 물론, 리스트 캄프리헨션도 포함한다.

캄프리헨션은 데이터베이스를 위한 쿼리 표기법으로 제안되었으며<ref>[http://portal.acm.org/citation.cfm?coll=GUIDE&dl=GUIDE&id=135271 Comprehensions, a query notation for DBPLs]</ref> ''Kleisli''   데이터베이스 쿼리 언어로 구현되었다.<ref>[http://portal.acm.org/citation.cfm?id=351241&dl=ACM&coll=portal The functional guts of the Kleisli query system]</ref>

== 다른 프로그래밍 언어의 예 ==
{{빈 문단}}

== 유사 구조 ==

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

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

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

=== 병렬 리스트 캄프리헨션 ===
Glasgow Haskell 컴파일러는 '''병렬 리스트 캄프리헨션''' (또는 '''zip-캄프리헨션'''이라고도 알려져 있는) 한 가지 예외를 갖고 있는데, 리스트 캄프리헨션 내에 다양하고 독립적인 수식어 분기가 가능하다. 반면, 콤마로 구분되는 수식어는 종속적("내재된")이며, 파이프로 구분된 수식어 분기는 병렬로 수행된다(이는 어떠한 형태의 멀티스레드 방식도 의미하지 않는다: 단지 분기가 [[Map (고차 함수)|매핑]]된다는 것을 의미할 뿐이다).<syntaxhighlight lang="haskell">
-- regular list comprehension
a = [(x,y) | x <- [1..5], y <- [3..5]]
-- [(1,3),(1,4),(1,5),(2,3),(2,4) ...

-- zipped list comprehension
b = [(x,y) | (x,y) <- zip [1..5] [3..5]]
-- [(1,3),(2,4),(3,5)]

-- parallel list comprehension
c = [(x,y) | x <- [1..5] | y <- [3..5]]
-- [(1,3),(2,4),(3,5)]
</syntaxhighlight>Racket의 캄프리헨션 표준 라이브러리는 캄프리헨션에 대한 병렬과 내재된 버전을 포함하는데, 이름에서 "for"와 "for*"로 구분된다. 예를 들어, 벡터 캄프리헨션인 "for/vector"와 "for*/vector"는 각각 시퀀스에 대한 병렬 및 내재된 이터레이션을 생성한다. 다음은 Haskell 리스트 캄프리헨션 예제에 대한 Racket 코드다.<syntaxhighlight lang="scheme">
> (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))
</syntaxhighlight>Python에서는 다음과 같이 할 수 있다:<syntaxhighlight lang="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)]
</syntaxhighlight>

=== XQuery와 XPath ===
NPL의 본래 용도와 마찬가지로, 이들은 원래 데이터베이스 액세스 언어다.

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

XPath의 표현은 다음과 같다:<syntaxhighlight lang="xquery">
 /library/book//paragraph[@style='first-in-chapter']
</syntaxhighlight>이는 개념적으로 연속된 "단계"들로 계산되는데, 여기서 각 단계는 리스트를 만들어내며 다음 단계는 이전 단계의 출력에 대한 각각의 요소에 필터 함수를 적용한다.<ref>{{웹 인용|url=http://www.w3.org/TR/xpath#section-Location-Steps|title=2.1 Location Steps|work=XML Path Language (XPath)|date=16 November 1999|publisher=[[W3C]]|확인날짜=2017-11-19|보존url=https://web.archive.org/web/20121209085946/http://www.w3.org/TR/xpath/#section-Location-Steps|보존날짜=2012-12-09|url-status=dead}}</ref>

XQuery에서, 전체 XPath를 사용할 수 있지만, FLWOR 구문 또한 사용 가능한데, 이는 좀 더 강력한 캄프리헨션 구조다.<ref>{{웹 인용|url=http://www.w3schools.com/XQuery/xquery_flwor.asp|title=XQuery FLWOR Expressions|work=[[W3Schools]]|확인날짜=2011년 10월 7일|보존url=https://web.archive.org/web/20111008001258/http://w3schools.com/xquery/xquery_flwor.asp|보존날짜=2011년 10월 8일|url-status=dead}}</ref><syntaxhighlight lang="xquery">
 for $b in //book
 where $b[@pages < 400]
 order by $b//title
 return
   <shortBook>
     <title>{$b//title}</title>
     <firstPara>{($book//paragraph)[1]}</firstPara>
   </shortBook>
</syntaxhighlight>여기서 XPath인 //book가 수행될 경우 (리스트라고도 하는) 하나의 시퀀스가 생성된다; where문은 함수적인 "필터"이고, order by문은 결과를 정렬하며,  {{Tag|shortBook}} XML 스니펫은 실제로 다른 함수형 언어에서 볼 수 있는 'map'을 사용해 시퀀스 내 각각의 요소에 대해 XML을 빌드하거나 변형하는 익명 함수다.

그래서, 위에서 본 FLWOR 구문은 다른 함수형 언에서 다음과 같이 구현될 수 있다:<syntaxhighlight lang="xquery">
 map(
   newXML(shortBook, newXML(title, $1.title), newXML(firstPara, $1...))
   filter(
     lt($1.pages, 400),
     xpath(//book)
   )
 )
</syntaxhighlight>

=== C#의 LINQ ===
[[C 샤프|C#]] 3.0은 관련 기능 중 LINQ라고 불리는 그룹을 가지고 있는데, 객체 열거를 처리하기 위한 쿼리 동작 집합을 정의한다.<syntaxhighlight lang="csharp">
var s = Enumerable.Range(0, 100).Where(x => x*x > 3).Select(x => x*2);
</syntaxhighlight>또한 추억의 SQL에 대한 대안으로 캄프리헨션 문법을 제공하기도 한다:<syntaxhighlight lang="csharp">
var s = from x in Enumerable.Range(0, 100) where x*x > 3 select x*2;
</syntaxhighlight>LINQ는 전형적인 리스트 캄프리헨션 구현에 대한 기능을 제공한다. 캄프리헨션의 최상위 객체가 캄프리헨션에 대한 체인 메서드를 실행하지 않고 , IQueryable 인터페이스를 구현하는 경우, 명령의 전체 시퀀스는 AST(Abstract Syntax Tree) 객체로 변환되는데, 이는 IQueryable 객체로 전달되어 해석되고 실행된다.

덕분에 다른 것들보다도 IQueryable을 위해 다음 두 가지를 가능케한다:
* 비호환적되거나 비효율적인 캄프리헨션의 재작성
* 예외를 위해 AST를 또 다른 쿼리 언어(예를 들어, SQL)로 변환

=== C++ ===
C++는 리스트 캄프리헨션을 직접적으로 지원하는 어떠한 언어적 기능도 갖고 있지 않지만 [[연산자 오버로딩]] (예를 들어, |, >>, >>=의 재정의)은 "내장" 쿼리 [[도메인 특화 언어|DSL]]을 위한 표현 문법을 제공하는데 잘 사용되어 왔다. 그게 아니면, 리스트 캄프리헨션은 컨테이너 내 요소들을 선택하기 위한 erase-remove idiom과 그 요소들을 변형하기 위한 STL 알고리즘 for_each를 사용해 구성될 수 있다.<syntaxhighlight lang="cpp">
#include <algorithm>
#include <list>
#include <numeric>

using namespace std;

template<class C, class P, class T>
C comprehend(C&& source, const P& predicate, const T& transformation)
{
  // initialize destination
  C d = forward<C>(source);

  // filter elements
  d.erase(remove_if(begin(d), end(d), predicate), end(d));

  // apply transformation
  for_each(begin(d), end(d), transformation);

  return d;
}

int main()
{
  list<int> range(10);
      // range is a list of 10 elements, all zero
  iota(begin(range), end(range), 1);
      // range now contains 1,2,...,10

  list<int> result = comprehend(
      range,
      [](int x){return x*x <= 3;},
      [](int &x){x *= 2;});
      // result now contains 4,6,...,20
}
</syntaxhighlight>C++에 집합-빌더 표기법과 유사한 리스트 캄프리헨션/구문을 제공하기 위한 노력들이 있다.
* [[Boost]].Range 라이브러리에는 어떤 범위에든 적용할 수 있고, 필터링 및 변환 등을 할 수 있는 어댑터 표기법이 있다. 이 라이브러리와 함께, 앞서 본 Haskell 예제를 (익명 필터와 변형 기능을 위해 Boost.Lambda를 사용해) 다음과 같이 작성할 수 있다([http://codepad.org/y4bpgLJu 전체 예제] {{웨이백|url=http://codepad.org/y4bpgLJu |date=20110720202705 }}):<syntaxhighlight lang="cpp">
counting_range(1,10) | filtered( _1*_1 > 3 ) | transformed(ret<int>( _1*2 ))
</syntaxhighlight>
* 다음<ref>{{웹 인용|url=http://mfoliveira.org/blog/2011/01/04/simple-list-comprehension-in-cp-using-preprocessor-macros/|title=Single-variable List Comprehension in C++ using Preprocessor Macros|확인날짜=2017년 11월 19일|보존url=https://web.archive.org/web/20110821211656/http://mfoliveira.org/blog/2011/01/04/simple-list-comprehension-in-cp-using-preprocessor-macros/|보존날짜=2011년 8월 21일|url-status=dead}}</ref> 구현은 매크로를 사용하며 << 연산자 재정의한다. 'if' 내부의 모든 표현식이 유효한지를 평가하며, 어떤 변수 이름이든지 선택될 수 있다. 하지만, 이는 [[스레드 안전|스레드에 안전]]하지 않다. 사용 예제:<syntaxhighlight lang="cpp">
list<int> N;
list<double> S;

for (int i = 0; i < 10; i++)
     N.push_back(i);

S << list_comprehension(3.1415 * x, x, N, x*x > 3)
</syntaxhighlight>
* 다음<ref>{{웹 인용|url=http://www.tedunangst.com/listcc.html|title=C++ list comprehensions|확인날짜=2017-11-19|보존url=https://web.archive.org/web/20170707125836/http://www.tedunangst.com/listcc.html|보존날짜=2017-07-07|url-status=dead}}</ref> 구현은 클래스 및 연산자 오버로딩을 사용한 head/tail 슬라이싱과 (함수를 사용한) 리스트 필터링을 위한 | 연산자를 제공한다. 사용 예제:<syntaxhighlight lang="cpp">
bool even(int x) { return x % 2 == 0; }
bool x2(int &x) { x *= 2; return true; }

list<int> l, t;
int x, y;

for (int i = 0; i < 10; i++)
     l.push_back(i);

(x, t) = l | x2;
(t, y) = t;

t = l < 9;
t = t < 7 | even | x2;
</syntaxhighlight>
* 내장 쿼리와 순회를 위한 언어 (LEESA<ref>{{웹 인용|url=http://www.dre.vanderbilt.edu/LEESA/|title=Language for Embedded Query and Traversal (LEESA)}}</ref>)는 C++에 내장된 DSL로 연산자 오버로딩을 사용해 X-Path와 같은 쿼리를 구현한다. 이 쿼리는 XSD에서  xml-to-c++ 바인딩을 사용해 얻은 바인딩된 충분한 형식의 xml 트리 상에서 실행된다. 문자열 인코딩은 전혀 없다. xml 태그 이름 조차도 클래스이므로, 오타가 날 방법은 없다. LEESA 표현식이 데이터 모델 내에 존재치 않는 잘못된 경로를 형성할 경우, C++ 컴파일러는 코드를 거부한다. a catalog xml을 생각해보자.
* <syntaxhighlight lang="xml">
<catalog>
  <book>
    <title>Hamlet</title>
    <price>9.99</price>
    <author>
      <name>William Shakespeare</name>
      <country>England</country>
    </author>
  </book>
  <book>...</book>
...
</catalog>
</syntaxhighlight>
LEESA는 X-Path의 / 구분자를 위한 >>를 제공한다. 흥미롭게도, 트리 내 중간 노드를 "건너뛰는" X-Path의 // 구분자는 LEESA 내에서 전략적인 프로그래밍으로 알려져 있는 것들을 사용해 구현된다. 아래 예제에서,  catalog_, book_, author_, 그리고 name_은 각각 catalog, book, author, 그리고 name 클래스다.<syntaxhighlight lang="cpp">
// Equivalent X-Path: "catalog/book/author/name"
std::vector<name> author_names = 
evaluate(root, catalog_ >> book_ >> author_ >> name_);

// Equivalent X-Path: "catalog//name"
std::vector<name> author_names = 
evaluate(root, catalog_ >> DescendantsOf(catalog_, name_));

// Equivalent X-Path: "catalog//author[country=="England"]"
std::vector<name> author_names = 
evaluate(root, catalog_  >> DescendantsOf(catalog_, author_)
                         >> Select(author_, [](const author & a) { return a.country()=="England"; })
                         >> name_);
</syntaxhighlight>

== 같이 보기 ==
* [[SQL]]에서 FROM과 WHERE문과 함께 사용되는 [[Select (SQL)|SELECT]]문

== 각주 ==
{{각주}}
* [https://web.archive.org/web/20050125080818/http://ftp.sunet.se/foldoc/foldoc.cgi?list+comprehension List Comprehension] in The Free On-line Dictionary of Computing, Editor Denis Howe.

=== Haskell ===
* The Haskell 98 Report, chapter [http://haskell.org/onlinereport/exps.html#list-comprehensions 3.11 List Comprehensions].
* The Glorious Glasgow Haskell Compilation System User's Guide, chapter [https://web.archive.org/web/20051129140339/http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#parallel-list-comprehensions 7.3.4 Parallel List Comprehensions].
* The Hugs 98 User's Guide, chapter [https://web.archive.org/web/20140515114545/http://cvs.haskell.org/Hugs/pages/users_guide/hugs-ghc.html#ZIP-COMPREHENSION 5.1.2 Parallel list comprehensions (a.k.a. zip-comprehensions)].

=== OCaml ===
* [http://batteries.forge.ocamlcore.org/ OCaml Batteries Included]
* [http://batteries.forge.ocamlcore.org/doc.preview:batteries-alpha3/html/extensions.html Language extensions introduced in OCaml Batteries Included] {{웨이백|url=http://batteries.forge.ocamlcore.org/doc.preview:batteries-alpha3/html/extensions.html |date=20160303172057 }}

=== Python ===
* The Python Tutorial, [https://docs.python.org/tutorial/datastructures.html#list-comprehensions List Comprehensions].
* Python Language Reference, [https://docs.python.org/reference/expressions.html#list-displays List displays].
* Python Enhancement Proposal [https://www.python.org/peps/pep-0202.html PEP 202: List Comprehensions].
* Python Language Reference, [https://docs.python.org/reference/expressions.html#generator-expressions Generator expressions].
* Python Enhancement Proposal [https://python.org/peps/pep-0289.html PEP 289: Generator Expressions].

=== Common Lisp ===
* [http://rali.iro.umontreal.ca/Publications/urls/LapalmeLispComp.pdf Implementation of a Lisp comprehension macro] by Guy Lapalme

=== Clojure ===
* [https://clojuredocs.org/clojure.core/for Clojure API documentation - ''for'' macro]

=== Axiom ===
* [https://web.archive.org/web/20051018040438/http://page.axiom-developer.org/zope/mathaction/Streams Axiom stream examples]

== 외부 링크 ==
* SQL-like set operations with list comprehension one-liners in the [http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/159974 Python Cookbook] {{웨이백|url=http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/159974 |date=20080702210432 }}
* [http://lambda-the-ultimate.org/classic/message11326.html Discussion on list comprehensions in Scheme and related constructs]
* [http://langexplr.blogspot.com/2007/02/list-comprehensions-across-languages_18.html List Comprehensions across languages]
[[분류:프로그래밍 구성체]]