それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

WebAPIの無駄呼び出し回数をGeneratorで減らしつつ、コードの書き換えはできる限り減らしたい。

二律背反の要求を実現したい。

WebAPIの無駄な呼び出し回数を少しでも減らしたい。回数を減らし、プログラム応答速度を上げたい。

ただ、コードの書き換えは極小にしたい。

この目的では、Generatorを使うのが、鉄板だと思うんですよね。

WEBを呼び出すコードのサンプル。

次のようなAPIを叩くコードがあってさ。

<?php
function my_list_items(){
  foreach( $api->call('list_item') as $id){
    $list[]=$api->call('get_item', $id);
  }
  return $list;
}
$items = $my_list_items();
$item = $items[0];

要素を全部使わないのに、毎回全部取ってしまうんですよね。もったいないですよね。API呼び出し回数に時間あたり制限とかあったり、高頻度呼び出しが攻撃判定されちゃったりして辛いんですよね。

Generator ( yield ) で解決する。

yield を使えば、だいたい解決する。

<?php
function my_list_items(){
  foreach(  $api->call('list_item') as $id){
    $item  = $api->call('get_item', $id);
    yield $item; //<=yield を使って書き換える。
  }
}
//
$items = $my_list_items();
$item = $items[0];//<= ここがエラーになる。

yield を使えばいいの。だいたい解決する。だけど、yieldの結果はGeneratorである。GeneratorだとArrayとはアクセス方法が異なる。foreach互換性(ある程度)がある。ただ、Arrayとの互換性が全くない。コードの修正箇所が増えてしまう。修正箇所を探すだけでも困難だ。

<?php
$item = $items[0];//<= 配列のアクセスが使えない
$item = $items->corrent();//<= メソッドを使う必要がある。

とくに、current()のようなメソッドを使って先頭を取るのが、ソースコードの可読性を著しく損なうというか。逆に型がわかりにくい。

CachingIterarorを使う。

php の場合。CachingIteraror を使うのが鉄板だが、CachingIterarorには問題がある。

<?php
function my_list_items(){
  return new \CachingIteraror((function(){
  foreach(  $api->call('list_item') as $id){
    $item  = $api->call('get_item', $id);
    yield $item;
  }  
  })());
}
$items = $my_list_items();
$item = $items[0];//<= BadMethodCallException になる。

CachingIterarorだと、一見すると解決しそうなんだけど、キャッシュするまでは、BadMethodCallException になる。( php 8.2 でも)

一度キャッシュする必要がある。

<?php
$items = $my_list_items();
$item = $items[0];//<= BadMethodCallException になる。

キャッシュするまでアクセス不能で、キャッシュ後は気軽にアクセスできるって、コードの見通しが良くない。配列でアクセスしたいのか、手続きに他の要因が関連してるように見えてしまい、コードのメンテナンス性能を喪失してしまう。

<?php
$item = $items[0];//<= BadMethodCallException になる。
$items->corrent();//<=キャッシュする。
$item = $items[0];//<= アクセス可能になる。

こんな問題があってGeneratorは避けてたんだけど、APIコールが遅いのでなにか手を考える必要があって。やっぱりGeneratorしか無い。となるが、Generatorを見せないで使いたい。

そのうち、公式で解決しそうなんだけど。解決しそうなんだけど、って思ってたのだが、公式では解決しそうに無い。今後の更新も型システムについてばかり。*1

中間クラスがほしい。

ArrayAccessをサポートしつつCachingもサポートしたそんなクラスがほしい。

<?php
function my_list_items(){
  return new \MySomeClass((function(){
  foreach(  $api->call('list_item') as $id){
    $item  = $api->call('get_item', $id);
    yield $item;
  }  
  })());
}
$items = $my_list_items();
$item = $items[0];//<= コードの書き換えがない。

とりあえず作った。

yieldを使うときの、このような問題を手軽に解決したくて、コードを書きました。

github.com

もっといい解決方法はないのだろうか。

*1:PhpStromのようなIDEを使わないカジュアルなPHP'erの存在が裾野の広さで、そこがエコシステムの根底なのに、その裾野をバッサリ切る感じはちょっとどうかと思う。正直言ってPHPは型の硬さが入ることで相当数の利用者が離脱し、便利さが再発見されるまでに相当時間がかかりそう。