Skip to content

ジェネレータで無限を手玉に取る術

公開日:

東京都練馬区立区民・産業プラザ Coconeriホールで開催された『PHPerKaigi 2020』でレギュラートーク(15分)として発表しました。

Download PDF

スライドテキスト

Page 1

ジェネレータで

無限を手玉に取る術

Techniques for controlling infinity using generator

2020-02-11 PHPerKaigi 2020 Day 2

Coconeri Hall, Nerima, Tokyo #phperkaigi

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • ピクシブ株式会社 pixiv運営本部
  • Emacs Lisper, PHPer, Rubyist
  • Emacs PHP Modeのメンテナ引き継ぎました
  • 好きなリスプはEmacs Lispです
  • Qiitaに記事を書いたり変なコメントしてるよ

Page 3

Page 4

Page 5

Page 6

近況

Page 7

Page 8

本日のお題

Page 9

‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020

https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 10

無限を手玉に取る

Page 11

元ねたがある

Page 12

Page 13

いますぐ皆さんに函数プログラミングを学べという話ではない

Page 14

(個人的に)大事なのは以下のページ

Page 15

Page 16

Page 17

Page 18

言ってることはよくわからんがカッコイイ

Page 19

何が言いたいのか?

Page 20

遅延評価必要になるまで計算を遅らせる

Page 21

関数型知識ゼロでもその神髄の一端をいとも簡単に扱える機能がPHPにはある

Page 22

ここまで言えばおわかりですね?

Page 23

ジェネレータ

Generator

Page 24

ジェネレータとは何か?

Page 25

「PHPでforeachで反復できるものは何でしょう?」配列、オブジェクト、Iterator、そして“ジェネレータ”です。ジェネレータは言語によってコルーチン、semicoroutineやFiberとも呼ばれるものと同等の言語機能であり、SICPのような計算機科学の教科書で説明されるストリーム・無限リストを簡潔に実現できます。foreachループで配列を反復するのは既に作成済みのデータを頭から辿っていくだけのものであるのに対して、ジェネレータは配列のキーや要素に相当するものを計算によって生み出すことができるため、省メモリで効率のよい計算が可能です。このトークではジェネレータの基礎概念と、実際に応用できるパターンについて紹介していきます。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 26

この説明で全部なのですが…

Page 27

ざっくり説明していきます

Page 28

「PHPでforeachで反復できるものは何でしょう?」配列、オブジェクト、Iterator、そして“ジェネレータ”です。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 29

ジェネレータはforeachできる

配列を代用できる場面がある

全てを置き換えるものではない

あくまでforeachで順次実行するもののみ

その他の反復可能オブジェクトについては今回触れません

Page 30

iterable擬似型 (PHP 7.1)

擬似型とは実態のある一種類の型ではなく同じように扱える複数の型とクラスをひとまとめにした便宜上のもの

iterable = foreachできる値

Page 31

おまけ: 擬似型

前述したように特定の型ではないが、型宣言(引数・戻り値)の型として記述できる

callable擬似型関数として呼び出せる値

Page 32

おまけ: iterableと型定義

DocCommentに型を書くときキーと値の型を書ける

iterable<int,string>やGenerator<int,string>のように

PhpStormは未対応なのでiterable|string[] で代用…

Page 33

おまけ: iterableと型定義

iterableの型をちゃんと書いておくとforeachの中身も静的解析できる

PHPStanは書かないと叱ってくれる

JetBrainsさん早くPhpStorm対応して(deep-assocプラグインで補完可能)

詳細は「2018年のPHPDoc事情とPSR-5」に書きました

Page 34

置換できる可能性がある

<?php

// どこかから取得してきたIDリスト

$target_ids = [85490, 54352, 34242, 45090];

foreach ($target_ids as $id) {shori($id);}

Page 35

置換できる可能性がある

<?php

// ジェネレータのイメージ

$target_ids = function () { ... };

foreach ($target_ids as $id) {shori($id);}

Page 36

ジェネレータは言語によってコルーチン、semicoroutineやFiberとも呼ばれるものと同等の言語機能であり、‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 37

用語 (呼ばれかた)

言語によって呼びかたや機能が若干違います

JavaScriptやPythonのジェネレータはPHPと似ています

RubyにはEnumeratorとFiberというものがあります

Page 38

コルーチン (coroutine)

ジェネレータは古典的なコルーチンと同等のものですが、現代でコルーチンと呼ばれているものは意味がかなり拡大しているので注意が必要です

Page 39

SICPのような計算機科学の教科書で説明されるストリーム・無限リストを簡潔に実現できます。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 40

SICP

計算機プログラムの構造と解釈

Page 41

‒計算機プログラムの構造と解釈 第2版

https://www.amazon.co.jp/dp/4798135984

Page 42

Webで無料でも読めます

Page 43

‒計算機プログラムの構造と解釈 第2版

https://sicp.iijlab.net/fulltext/

Page 44

‒アスペ日記 非公式PDF版SICP・新訳

https://takeda25.hatenablog.jp/entry/20151030/1446174031

Page 45

計算機科学の入門書という扱いだが、簡単な本ではない

Page 46

この本の序盤2章あたりに書いてあること

Page 47

(二値の組を持つオブペアジェクト)があれば様々なデータ構造が作れる

Page 48

ペアの片方を関数で遅延させることでストリーム(遅延リスト)というものが作れる

Page 49

ストリーム (遅延リスト)

配列・オブジェクトのようなものとクロージャがあれば実現できる

PHPで実装してみた

https://github.com/zonuexe/phperkaigi-generator/blob/master/stream.php

Page 50

ストリームでできることはジェネレータで表現できる

Page 51

foreachループで配列を反復するのは既に作成済みのデータを頭から辿っていくだけのものであるのに対して、ジェネレータは配列のキーや要素に相当するものを計算によって生み出すことができるため、省メモリで効率のよい計算が可能です。‒ジェネレータで無限を手玉に取る術 PHPerKaigi 2020 https://fortee.jp/phperkaigi-2020/proposal/5b1651a5-0a01-4b49-b338-e332f981bf5d

Page 52

ということで、ジェネレータを説明します

Page 53

ジェネレータの書きかた

Page 54

ジェネレータ=特別な関数

関数定義(メソッド・クロージャを含む)の中でyieldと書くとジェネレータになる

yieldに渡した値はforeachの値として受け渡される

Page 55

PHPUnit dataProvider

テストケースに必要なパラメータを生成するメソッド

配列を返すように作るのが一般的だが、実はジェネレータでも作れる

Page 56

基本的なジェネレータ

<?php

// どこかから取得してきたIDリスト

f$target_ids = [85490, 54352, 34242, 45090];

foreach ($target_ids as $id) {shori($id);}

Page 57

基本的なジェネレータ

<?php

// どこかから取得してきたIDリスト

f$target_ids = [85490, 54352, 34242, 45090];

foreach ($target_ids as $id) {shori($id);}