-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathgenerators.xml
601 lines (526 loc) · 17.1 KB
/
generators.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 08e58ace7e5b538c8ed75d784a54885d5f785d30 Maintainer: takagi Status: ready -->
<!-- Credits: mumumu -->
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
<title>ジェネレータ</title>
<sect1 xml:id="language.generators.overview">
<title>ジェネレータとは</title>
<?phpdoc print-version-for="generators"?>
<para>
ジェネレータを使えば、シンプルな
<link linkend="language.oop5.iterations">イテレータ</link>を簡単に実装できます。
<classname>Iterator</classname> インターフェイスを実装したクラスを用意する
オーバーヘッドや複雑さを心配する必要はありません。
</para>
<para>
ジェネレータを使うと、&foreach; でデータ群を順に処理するコードを書くときに
メモリ内で配列を組み立てなくても済むようになります。
メモリ内で配列を組み立てると memory_limit を越えてしまうかもしれないし、
無視できないほどの時間がかかってしまうかもしれません。
配列を作る代わりに、ジェネレータ関数を書くことになります。これは通常の
<link linkend="functions.user-defined">関数</link>と同じものですが、
ジェネレータ関数は一度だけ
<link linkend="functions.returning-values">return</link>
するのではなく、必要に応じて何度でも &yield; することができます。
つまり、値を繰り返し返せるということです。
イテレーターと同じく、ランダムアクセスはできません。
</para>
<para>
シンプルな例として、<function>range</function> 関数をジェネレータで実装しなおしてみましょう。
標準の <function>range</function> 関数は、すべての値を含む配列を作ってそれを返します。
結果的に、かなり大きな配列になる可能性があります。たとえば
<command>range(0, 1000000)</command> を実行すると、
100 MB を超えるメモリを使うことになります。
</para>
<para>
その代替として、ジェネレータ <literal>xrange()</literal> を実装します。
必要なメモリは、<classname>Iterator</classname>
オブジェクトを作ってジェネレータの内部の状態を記録しておくのに必要なものだけになります。
1 KB 未満で収まるでしょう。
</para>
<example>
<title>ジェネレータを使った <function>range</function> の実装</title>
<programlisting role="php">
<![CDATA[
<?php
function xrange($start, $limit, $step = 1) {
if ($start <= $limit) {
if ($step <= 0) {
throw new LogicException('Step must be positive');
}
for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if ($step >= 0) {
throw new LogicException('Step must be negative');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
/*
* 次の例で、range() と xrange() が同じ結果を返すことに注目しましょう
*/
echo 'Single digit odd numbers from range(): ';
foreach (range(1, 9, 2) as $number) {
echo "$number ";
}
echo "\n";
echo 'Single digit odd numbers from xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
echo "$number ";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Single digit odd numbers from range(): 1 3 5 7 9
Single digit odd numbers from xrange(): 1 3 5 7 9
]]>
</screen>
</example>
<sect2 xml:id="language.generators.object">
<title><classname>Generator</classname> オブジェクト</title>
<para>
ジェネレータ関数を呼んだときには、内部クラス
<classname>Generator</classname> の新しいオブジェクトを返します。
このオブジェクトは <classname>Iterator</classname> インターフェイスを実装しており、
前進しかできないイテレータオブジェクトと同じように振る舞います。
そして、このオブジェクトが提供するメソッドを使えば、
値を送ったり戻したりなどしてジェネレータの状態を操作できます。
</para>
</sect2>
</sect1>
<sect1 xml:id="language.generators.syntax">
<title>ジェネレータの構文</title>
<para>
ジェネレータ関数の見た目はふつうの関数とほぼ同じです。違うのは、値を返すのではなく、
必要なだけ値を &yield; することです。
&yield; が含まれていれば、どんな関数でもジェネレータ関数です。
</para>
<para>
ジェネレータ関数が呼ばれると、反復処理が可能なオブジェクトを返します。
このオブジェクトを (&foreach; ループなどで) 反復させると、
値が必要になるたびに PHP がオブジェクトの反復メソッドを呼びます。
そして、ジェネレータが値を yield した時点の状態を保存しておき、
次に値が必要になったときにはそこから再開できるようにします。
</para>
<para>
yield できる値がなくなると、ジェネレータは単純に呼び出し元に制御を戻します。
呼び出し元のコードでは、配列の要素をすべて処理し終えた後のように、そのまま処理が続きます。
</para>
<note>
<para>
ジェネレータは値を返すことができます。返した値は
<methodname>Generator::getReturn</methodname> で取得することが出来ます。
</para>
</note>
<sect2 xml:id="control-structures.yield">
<title><command>yield</command> キーワード</title>
<para>
ジェネレータ関数の肝となるのが <command>yield</command> キーワードです。
最もシンプルな書きかたをすると、yield 文の見た目は return 文とほぼ同じになります。
ただ、return の場合はそこで関数の実行を終了して値を返すのに対して、
yield の場合はジェネレータを呼び出しているループに値を戻して
ジェネレータ関数の実行を一時停止します。
</para>
<example>
<title>値を yield する単純な例</title>
<programlisting role="php">
<![CDATA[
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// yield を繰り返す間、$i の値が維持されることに注目しましょう
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1
2
3
]]>
</screen>
</example>
<note>
<para>
内部的には整数の連番のキーが yield する値とペアになり、
配列と同じようになります。
</para>
</note>
<sect3 xml:id="control-structures.yield.associative">
<title>値とキーの yield</title>
<para>
PHP は、数値添字の配列だけでなく連想配列にも対応しています。ジェネレータも例外ではありません。
先ほどの例のように単なる値を yield するだけでなく、
値と同時にキーも yield することができます。
</para>
<para>
キーと値のペアを yield する構文は連想配列の定義とよく似ており、次のようになります。
</para>
<example>
<title>キー/値 のペアの yield</title>
<programlisting role="php">
<![CDATA[
<?php
/*
* 入力は各フィールドをセミコロンで区切ったものです
* 最初のフィールドが ID となり、これをキーとして使います
*/
$input = <<<'EOF'
1;PHP;$が大好き
2;Python;インデントが大好き
3;Ruby;ブロックが大好き
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";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1:
PHP
$が大好き
2:
Python
インデントが大好き
3:
Ruby
ブロックが大好き
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.null">
<title>null 値の yield</title>
<para>
何も引数を渡さずに yield を呼ぶと、&null; 値を yield します。キーは自動的に割り振られます。
</para>
<example>
<title>&null; の yield</title>
<programlisting role="php">
<![CDATA[
<?php
function gen_three_nulls() {
foreach (range(1, 3) as $i) {
yield;
}
}
var_dump(iterator_to_array(gen_three_nulls()));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
NULL
[1]=>
NULL
[2]=>
NULL
}
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.references">
<title>参照による yield</title>
<para>
ジェネレータ関数は、値を参照として yield することもできます。
<link linkend="functions.returning-values">関数の結果を参照で返す</link>
ときと同じように、関数名の前にアンパサンドを付けます。
</para>
<example>
<title>参照による値の yield</title>
<programlisting role="php">
<![CDATA[
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
/*
* $number をループ内で変更していることに注目しましょう。
* このジェネレータは参照を yield するので、
* gen_reference() 内の $value が変わります。
*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
2... 1... 0...
]]>
</screen>
</example>
</sect3>
<sect3 xml:id="control-structures.yield.from">
<title><command>yield from</command> によるジェネレータの委譲</title>
<para>
ジェネレーターを委譲することで、
別のジェネレータや <classname>Traversable</classname> オブジェクトあるいは配列から、
<command>yield from</command> キーワードを使って値を yield できます。
外側のジェネレータは、内側のジェネレータ (あるいはオブジェクトや配列) から受け取れるすべての値を yield し、
何も取得できなくなったら外側のジェネレータの処理を続行します。
</para>
<para>
ジェネレータに対して <command>yield from</command> を使った場合は、
<command>yield from</command> 式は内側のジェネレータが返す任意の値を返します。
</para>
<caution>
<title><function>iterator_to_array</function> を用いた、配列への格納</title>
<para>
<command>yield from</command> は配列のキーをリセットしません。
<classname>Traversable</classname> オブジェクトや <type>array</type>
が返すキーを、そのまま利用します。つまり、別々の
<command>yield</command> や <command>yield from</command>
から取得した異なる値のキーが、重複することもありえます。
これを配列に格納すると、後からきた値がそれまでの値を上書きします。
</para>
<para>
<function>iterator_to_array</function> を使う場合に問題になることがよくあります。
この関数はデフォルトで数値添字配列を返すので、予期せぬ結果を引き起こす可能性があります。
<function>iterator_to_array</function> には二番目のパラメータ
<parameter>preserve_keys</parameter> があり、これを &false;
にすれば、<classname>Generator</classname> が返すキーを無視してすべての値を取得できます。
</para>
<example>
<title><command>yield from</command> と <function>iterator_to_array</function></title>
<programlisting role="php">
<![CDATA[
<?php
function inner() {
yield 1; // キー 0
yield 2; // キー 1
yield 3; // キー 2
}
function gen() {
yield 0; // キー 0
yield from inner(); // キー 0〜2
yield 4; // キー 1
}
// 二番目のパラメータに false を指定すると、結果は array [0, 1, 2, 3, 4] となります
var_dump(iterator_to_array(gen()));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
]]>
</screen>
</example>
</caution>
<example>
<title><command>yield from</command> の基本的な使いかた</title>
<programlisting role="php">
<![CDATA[
<?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 ";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1 2 3 4 5 6 7 8 9 10
]]>
</screen>
</example>
<example>
<title><command>yield from</command> の返す値</title>
<programlisting role="php">
<![CDATA[
<?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();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
1 2 3 4 5 6 7 8 9 10
]]>
</screen>
</example>
</sect3>
</sect2>
</sect1>
<sect1 xml:id="language.generators.comparison">
<title>ジェネレータと <classname>Iterator</classname> オブジェクトとの比較</title>
<para>
ジェネレータの最大のメリットは、シンプルに書けることです。
<classname>Iterator</classname> を実装するのに比べて、必要な決まり文句の数がかなり少なくなります。
また、ジェネレータを使ったコードのほうが、一般的に読みやすくなります。
たとえば、次の関数とクラスを比べてみましょう。これらはどちらも同じ働きをするものです。
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
function getLinesFromFile($fileName) {
if (!$fileHandle = fopen($fileName, 'r')) {
return;
}
while (false !== $line = fgets($fileHandle)) {
yield $line;
}
fclose($fileHandle);
}
// これを、下のクラスと比べてみると……
class LineIterator implements Iterator {
protected $fileHandle;
protected $line;
protected $i;
public function __construct($fileName) {
if (!$this->fileHandle = fopen($fileName, 'r')) {
throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}
public function rewind() {
fseek($this->fileHandle, 0);
$this->line = fgets($this->fileHandle);
$this->i = 0;
}
public function valid() {
return false !== $this->line;
}
public function current() {
return $this->line;
}
public function key() {
return $this->i;
}
public function next() {
if (false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}
public function __destruct() {
fclose($this->fileHandle);
}
}
?>
]]>
</programlisting>
</informalexample>
<para>
しかし、柔軟性を実現するために犠牲にしていることもあります。
ジェネレータは前方にしか進めないイテレータなので、いったん反復処理が始まれば巻き戻すことができません。
これはつまり、同じジェネレータを何度も使い回せないということです。
ジェネレータ関数を呼んでもう一度作り直す必要があります。
</para>
</sect1>
<simplesect role="seealso">
&reftitle.seealso;
<para>
<simplelist>
<member><link linkend="language.oop5.iterations">オブジェクトの反復処理</link></member>
</simplelist>
</para>
</simplesect>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->