akanehara / ginq

`LINQ to Object` inspired DSL for PHP
MIT License
193 stars 18 forks source link

Added Ginq::iterate(), Ginq::unfold() #44

Closed akanehara closed 10 years ago

akanehara commented 10 years ago

いるよね

ritalin commented 10 years ago

欲しいね

ritalin commented 10 years ago

前に聞いたとき、unfoldに終端条件用意せず、無限シーケンスするといっていたけど、 途中できる場合も、末尾の次の要素を作る必要になる。

数値列なら気にならないだろうけど、コストの重いオブジェクト生成だと、気になると思うので、やっぱりOptional型でくるんだほうがいい気がする。

akanehara commented 10 years ago

Optionalが正しいのは間違いないけどPHP的にはやりすぎじゃないかしらと考えてlinq.js に倣って打ち切りを takeWhile に委ねよう、と考えてました。(もしSPLあたりにOption[A]相当品があるなら話は別)

でも、本件とは無関係にScalaでいうOption[A]みたいなクラスがPHPでも欲しくなる局面があるにはあるから、($x->getOrElse($alt)とか欲しいでしょ?)作ってもいいのかもしれない、とも。

akanehara commented 10 years ago

引数でもすでに null は特別扱いだし、いっそ null を None とみなすのはどうだろうか?

tanakahisateru commented 10 years ago

Optionは集合(イテレーション可能なもの)というより値(イテレーションできないもの)のレイヤーの問題じゃないかと思ったり

akanehara commented 10 years ago

iterate(a, a -> a) ではなく unfold(b, b -> [a, b]) のほうでは [] を Some、null を None とみなせば何の問題も起こさないないことに気がついた。[$x, $acc] で続行。null で打ち切り。

akanehara commented 10 years ago

もうひとつは、list を option のように使いうるという考え方を PHP の array でやっちゃう。つまりiterateならばシグニチャを iterate(a, a -> [a]) として、[a]の要素数が1なら Some、0 なら Noneというわけ。 このルールを unfold についても生真面目に適用すると、タプルがないぶん unfold(b, b -> [[a, b]]) となってうざいけどね。

akanehara commented 10 years ago

ちょっと iterateのことは置いといて、unfold を「ジェネレータ関数が 2 要素の配列を返す限り生成を続ける」というので feature ブランチ切ってみよう。

akanehara commented 10 years ago

おっと seed -> [v, seed] だと次のシードの計算が早すぎるのか。じゃあ、seed -> [v, () -> seed] ?

akanehara commented 10 years ago

自力で打ち切る unfold なら seed が Eagar だろうと Lazy だろうとたいした違いはないけど、takeWhile で外から打ち切るケースでは seed が Eagar だと 最後の seed の計算が無駄になる??

ritalin commented 10 years ago

iterationの仕方によるだろうけど、 closureの戻り値が、[後続のnext()対象, 次回のiteration]であるのなら、無駄になると思う。 どの程度無駄になるかは、使う側が返すObjectに依存するため、ライブラリ作成者には見積もれない。

ritalin commented 10 years ago

unfold戻り値の二番目がLazyなのは使う側からしたら扱いづらいと思う。 高階関数が高階関数を返す形になるから。 ただしiterate()内部の実装の詳細としてlazyなunfoldを使うのは問題ない。

akanehara commented 10 years ago

まさにそれで決着しそう。seed を Lazy にするかどうかは、Ginq::unfold の使い手まかせ。 そのうえで Ginq::iterate は、 seed が Lazy な Ginq::unfold で実装。

    /**
     * @param mixed    $seed
     * @param \Closure $generator seed -> ([v, seed] | null)
     * @return Ginq
     */
    public static function unfold($seed, $generator)
    {
        return self::from(self::$gen->unfold($seed, $generator));
    }

    /**
     * @param mixed    $initial
     * @param \Closure $generator v -> v
     * @return Ginq
     */
    public static function iterate($initial, $generator)
    {
        return self::unfold(
            $initial,
            function($seed) use ($generator) {
                $v = FuncUtil::force($seed);
                return array(
                    $v,
                    function () use ($v, $generator) { return $generator($v); }
                );
            }
        );
    }
ritalin commented 10 years ago

おまけ JavaのReflectionでちまちま親クラスたどるの面倒で、unfold作ったんだけど、 自分で打ち切らない場合、最後の戻り値が[null, null.getSuperClass()]となってしまうため、unfoldのclosureで次の状態を作る前に明示的なnullチェックが必要となり、unfoldのうまみが減る気がする。 このことも、打ち切りをunfoldの外にはしない方がいいと考えている理由の一つ。

// 明示的なnullチェック
// 多分こんな利用の仕方になってしまう
// うざい・・・
Ginq.unfold(obj.getClass(), function(seed) { return [seed, seed != null ? seed.getSuperClass() : null]; });

Lazyにすれば、nullチェックを省略できるんだろうけど、 高階関数の高階関数はちょっと・・・( ,,Ծ ‸ Ծ,,)

ritalin commented 10 years ago

JavaのCollection Streaming APIがiterate()としているので、この名前を採用したんだろうけど、正直いい名前とは思えない(個人的な感想です)。 無限数列を作るので、〜Infinity()とか、単純にinfinity()とかどんなもんでしょうか? ちなみにちなみに、F#ではinitInfinite(int -> T)で、iter は forEach的な操作となっておりまする。

akanehara commented 10 years ago

おっと、ScalaのStreamもHaskellのData.Listもiterateなので、考えもしなかった:sweat_smile: 通りがいいのはあればそれを採用したいかなあ。

akanehara commented 10 years ago

おっと、それ以前に take が無駄なフェッチしてることに気がついた。これは別 Issue で。

ritalin commented 10 years ago

ScalaのStreamもHaskellのData.Listもiterateなので

むしろF#が異端であったか・・・

akanehara commented 10 years ago

c363b5720b43f7f83fbaacdbe79d3d6f7003750d