제너레이터 문법
제너레이터 문법 (Generator syntax)
제너레이터 함수는 일반 함수와 모양은 같아 보이는데, 값을 하나 반환하는 대신 필요한 만큼 값을 **yield**로 내놓아요. yield를 포함하는 함수라면 어떤 함수든 제너레이터 함수가 돼요.
제너레이터 함수를 호출하면 반복 가능한(iterable) 객체 하나를 반환해요. 이 객체를 순회할 때, 예를 들어 foreach 루프로 돌리면, PHP는 값이 필요할 때마다 객체의 반복 메서드를 호출하고, 제너레이터가 값을 yield 하면 그 시점의 상태를 저장해 뒀다가 다음 값이 필요할 때 그 상태에서 이어서 실행해요.
더 이상 yield 할 값이 없으면 제너레이터는 그냥 반환하면 되고, 호출한 쪽의 코드는 배열에서 값이 떨어진 것처럼 이어서 실행돼요.
Note: 제너레이터는 값을 반환할 수도 있는데, 그 값은
Generator::getReturn()으로 가져올 수 있어요.
yield 키워드
제너레이터 함수의 핵심은 바로 yield 키워드예요. 가장 단순한 형태의 yield 문은 return 문과 비슷해 보이는데, 함수의 실행을 멈추고 값을 반환해 버리는 return과 달리, yield는 제너레이터를 순회하는 쪽 코드에 값을 하나 제공하고 제너레이터 함수의 실행을 잠시 멈춰요.
예제 #1 값을 yield 하는 간단한 예
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
위 예제의 출력은 다음과 같아요.
1
2
3
키와 함께 값 yield 하기
PHP는 연관 배열도 지원하니까 제너레이터도 마찬가지예요. 방금처럼 단순한 값만 yield 하는 게 아니라, 키(key)를 동시에 yield 할 수도 있어요. 키/값 쌍을 yield 하는 문법은 연관 배열을 정의할 때와 아주 비슷한데, 아래 예제에서 확인할 수 있어요.
예제 #2 키/값 쌍 yield 하기
<?php
/*
* The input is semi-colon separated fields, with the first
* field being an ID to use as a key.
*/
$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;
function input_parser($input) {
foreach (explode("\n", $input) as $line) {
$fields = explode(';', $line);
$id = array_shift($fields);
yield $id => $fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
위 예제의 출력은 다음과 같아요.
1:
PHP
Likes dollar signs
2:
Python
Likes whitespace
3:
Ruby
Likes blocks
null 값 yield 하기
인자 없이 yield를 호출하면 자동으로 키가 붙은 null 값을 yield 해요.
예제 #3 null yield 하기
<?php
function gen_three_nulls() {
foreach (range(1, 3) as $i) {
yield;
}
}
var_dump(iterator_to_array(gen_three_nulls()));
위 예제의 출력은 다음과 같아요.
array(3) {
[0]=>
NULL
[1]=>
NULL
[2]=>
NULL
}
참조(reference)로 yield 하기
제너레이터 함수는 값으로도, 참조로도 값을 yield 할 수 있어요. 이건 함수에서 참조를 반환하는 것과 같은 방법으로 하는데, 함수 이름 앞에 앰퍼샌드(&)를 붙이면 돼요.
예제 #4 참조로 값 yield 하기
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
/*
* Note that we can change $number within the loop, and
* because the generator is yielding references, $value
* within gen_reference() changes.
*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
위 예제의 출력은 다음과 같아요.
2... 1... 0...
yield from을 통한 제너레이터 위임
제너레이터 위임(Generator delegation)은 다른 제너레이터나 Traversable 객체, 또는 배열에서 yield from 키워드를 써서 값을 yield 할 수 있게 해줘요. 바깥 제너레이터는 안쪽 제너레이터·객체·배열의 값을 전부 yield 하고, 그것이 끝나면 그 후에 바깥 제너레이터의 실행이 이어져요.
제너레이터를 yield from과 함께 쓰면 yield from 표현식은 안쪽 제너레이터가 반환하는 값도 함께 반환해요.
Caution: 배열에 저장할 때(예:
iterator_to_array()로)yield from은 키를 리셋하지 않아요.Traversable객체나 배열이 반환한 키를 그대로 보존하죠. 그래서 어떤 값은 다른yield나yield from과 같은 키를 공유할 수 있고, 배열에 넣을 때 그 키의 이전 값을 덮어쓰게 돼요.이게 중요해지는 흔한 경우가
iterator_to_array()인데요, 기본값으로 키가 붙은 배열을 반환하다 보니 때로는 예상 밖의 결과가 나오기 쉬워요.iterator_to_array()에는 두 번째 인자preserve_keys가 있어서, 이것을false로 설정하면 제너레이터가 반환한 키를 무시하고 값을 전부 모을 수 있어요.
예제 #5 yield from과 iterator_to_array()
<?php
function inner() {
yield 1; // key 0
yield 2; // key 1
yield 3; // key 2
}
function gen() {
yield 0; // key 0
yield from inner(); // keys 0-2
yield 4; // key 1
}
// pass false as second parameter to get an array [0, 1, 2, 3, 4]
var_dump(iterator_to_array(gen()));
위 예제의 출력은 다음과 같아요.
array(3) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
예제 #6 yield from의 기본 사용
<?php
function count_to_ten() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from seven_eight();
yield 9;
yield 10;
}
function seven_eight() {
yield 7;
yield from eight();
}
function eight() {
yield 8;
}
foreach (count_to_ten() as $num) {
echo "$num ";
}
위 예제의 출력은 다음과 같아요.
1 2 3 4 5 6 7 8 9 10
예제 #7 yield from과 반환 값
<?php
function count_to_ten() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from seven_eight();
return yield from nine_ten();
}
function seven_eight() {
yield 7;
yield from eight();
}
function eight() {
yield 8;
}
function nine_ten() {
yield 9;
return 10;
}
$gen = count_to_ten();
foreach ($gen as $num) {
echo "$num ";
}
echo $gen->getReturn();
위 예제의 출력은 다음과 같아요.
1 2 3 4 5 6 7 8 9 10