제너레이터 문법

제너레이터 문법 (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 객체나 배열이 반환한 키를 그대로 보존하죠. 그래서 어떤 값은 다른 yieldyield from과 같은 키를 공유할 수 있고, 배열에 넣을 때 그 키의 이전 값을 덮어쓰게 돼요.

이게 중요해지는 흔한 경우가 iterator_to_array()인데요, 기본값으로 키가 붙은 배열을 반환하다 보니 때로는 예상 밖의 결과가 나오기 쉬워요. iterator_to_array()에는 두 번째 인자 preserve_keys가 있어서, 이것을 false로 설정하면 제너레이터가 반환한 키를 무시하고 값을 전부 모을 수 있어요.

예제 #5 yield fromiterator_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