Page 1
ジェネレータで
無限を手玉に取る術
Techniques for controlling infinity using generator
2020-02-11 PHPerKaigi 2020 Day 2
Coconeri Hall, Nerima, Tokyo #phperkaigi
公開日:
by USAMI Kenta@tadsan
に東京都の練馬区立区民・産業プラザ Coconeriホールで開催された『PHPerKaigi 2020』でレギュラートーク(15分)として発表しました。
Techniques for controlling infinity using generator
2020-02-11 PHPerKaigi 2020 Day 2
Coconeri Hall, Nerima, Tokyo #phperkaigi
近況
本日のお題
https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
無限を手玉に取る
元ねたがある
いますぐ皆さんに函数プログラミングを学べという話ではない
(個人的に)大事なのは以下のページ
言ってることはよくわからんがカッコイイ
何が言いたいのか?
遅延評価必要になるまで計算を遅らせる
関数型知識ゼロでもその神髄の一端をいとも簡単に扱える機能がPHPにはある
ここまで言えばおわかりですね?
ジェネレータ
ジェネレータとは何か?
「PHPでforeachで反復できるものは何でしょう?」配列、オブジェクト、Iterator、そして“ジェネレータ”です。ジェネレータは言語によってコルーチン、semicoroutineやFiberとも呼ばれるものと同等の言語機能であり、SICPのような計算機科学の教科書で説明されるストリーム・無限リストを簡潔に実現できます。foreachループで配列を反復するのは既に作成済みのデータを頭から辿っていくだけのものであるのに対して、ジェネレータは配列のキーや要素に相当するものを計算によって生み出すことができるため、省メモリで効率のよい計算が可能です。このトークではジェネレータの基礎概念と、実際に応用できるパターンについて紹介していきます。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
この説明で全部なのですが…
ざっくり説明していきます
「PHPでforeachで反復できるものは何でしょう?」配列、オブジェクト、Iterator、そして“ジェネレータ”です。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
ジェネレータはforeachできる
配列を代用できる場面がある
全てを置き換えるものではない
あくまでforeachで順次実行するもののみ
その他の反復可能オブジェクトについては今回触れません
iterable擬似型 (PHP 7.1)
擬似型とは実態のある一種類の型ではなく同じように扱える複数の型とクラスをひとまとめにした便宜上のもの
iterable = foreachできる値
おまけ: 擬似型
前述したように特定の型ではないが、型宣言(引数・戻り値)の型として記述できる
callable擬似型関数として呼び出せる値
おまけ: iterableと型定義
DocCommentに型を書くときキーと値の型を書ける
iterable<int,string>やGenerator<int,string>のように
PhpStormは未対応なのでiterable|string[] で代用…
おまけ: iterableと型定義
iterableの型をちゃんと書いておくとforeachの中身も静的解析できる
PHPStanは書かないと叱ってくれる
JetBrainsさん早くPhpStorm対応して(deep-assocプラグインで補完可能)
詳細は「2018年のPHPDoc事情とPSR-5」に書きました
<?php
// どこかから取得してきたIDリスト
$target_ids = [85490, 54352, 34242, 45090];
foreach ($target_ids as $id) {shori($id);}
<?php
// ジェネレータのイメージ
$target_ids = function () { ... };
foreach ($target_ids as $id) {shori($id);}
ジェネレータは言語によってコルーチン、semicoroutineやFiberとも呼ばれるものと同等の言語機能であり、‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
用語 (呼ばれかた)
言語によって呼びかたや機能が若干違います
JavaScriptやPythonのジェネレータはPHPと似ています
RubyにはEnumeratorとFiberというものがあります
コルーチン (coroutine)
ジェネレータは古典的なコルーチンと同等のものですが、現代でコルーチンと呼ばれているものは意味がかなり拡大しているので注意が必要です
SICPのような計算機科学の教科書で説明されるストリーム・無限リストを簡潔に実現できます。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
計算機プログラムの構造と解釈
https://www.amazon.co.jp/dp/4798135984
Webで無料でも読めます
https://sicp.iijlab.net/fulltext/
https://takeda25.hatenablog.jp/entry/20151030/1446174031
計算機科学の入門書という扱いだが、簡単な本ではない
この本の序盤2章あたりに書いてあること
(二値の組を持つオブペアジェクト)があれば様々なデータ構造が作れる
ペアの片方を関数で遅延させることでストリーム(遅延リスト)というものが作れる
ストリーム (遅延リスト)
配列・オブジェクトのようなものとクロージャがあれば実現できる
PHPで実装してみた
https://github.com/zonuexe/phperkaigi-generator/blob/master/stream.php
ストリームでできることはジェネレータで表現できる
foreachループで配列を反復するのは既に作成済みのデータを頭から辿っていくだけのものであるのに対して、ジェネレータは配列のキーや要素に相当するものを計算によって生み出すことができるため、省メモリで効率のよい計算が可能です。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d
ということで、ジェネレータを説明します
ジェネレータの書きかた
ジェネレータ=特別な関数
関数定義(メソッド・クロージャを含む)の中でyieldと書くとジェネレータになる
yieldに渡した値はforeachの値として受け渡される
PHPUnit dataProvider
テストケースに必要なパラメータを生成するメソッド
配列を返すように作るのが一般的だが、実はジェネレータでも作れる
<?php
// どこかから取得してきたIDリスト
f$target_ids = [85490, 54352, 34242, 45090];
foreach ($target_ids as $id) {shori($id);}
<?php
// どこかから取得してきたIDリスト
f$target_ids = [85490, 54352, 34242, 45090];
foreach ($target_ids as $id) {shori($id);}
