Functoolsで過剰にスマートなPHPを書こう (無名再帰篇)

More than 1 year has passed since last update.

こんにちは! 一年ほど前に作ったPHPのための函数プログラミング用ライブラリTeto Functoolsの利用者が全然増えないので、信者利用者を増やすために、べんり機能を紹介します。

インストール方法

はじめにDownload Composerを読んでcomposerをインストールしてね。

そして、Functoolsをインストール。

composer global require zonuexe/functools

既存のPHPライブラリに組込むにはこうします。

composer require zonuexe/functools

とりあえずglobal指定でインストールしたことを前提にコード書くよ。

無名再帰とは

無名再帰は、無名函数(ラムダ式とも呼ばれる)を使って再帰することだよ。具体的には不動点コンビネータってテクニックを利用するんだ。その中でも一番有名なのが「Yコンビネータ」だよ。ネットビジネスやベンチャーについて興味のあるひとはY Combinatorを知ってるはずだけど、このベンチャーキャピタルを率ゐるポール・グレアムは有名なLisperなんだ。

Haskell/不動点と再帰 - Wikibooksによれば、Haskellではfixって名前らしいから、恥も外聞もなくぱくってきたよ。カッコイイ用語に酔って中二心が溢れ出してきたのでYコンビネータを実装しようと思ったけど、Teto\Functools::fix()で採用されてるのは正確には正格評価の言語でも利用可能なよう変形した「Zコンビネータ」だよ。正格だけにね!

無名再帰のやりかた

PHPでショートコーディングしたいと思ふじゃないですか。

<?php
($f = function ($n, $max) use (&$f){
    if ($n <= $max) {
        printf("%dは%s".PHP_EOL, $n, ($n % 2 === 0) ? "偶数" : "奇数");
        $f($n + 1, $max);
    }
})(1, 20);

↑ このコードはPHP7では動くけど、PHP5系ではSyntax errorなんだ…!

<?php
use Teto\Functools as f;
require_once getenv('HOME') . '/.composer/vendor/autoload.php';

call_user_func(f::fix(function ($f) {
    return function ($n, $max) use ($f){
        if ($n <= $max) {
            printf("%dは%s".PHP_EOL, $n, ($n % 2 === 0) ? "偶数" : "奇数");
            $f($n + 1, $max);
        }
    };
}), 1, 20);

これでPHP5でも式プログラミングができるぞ! ついでに、見すぼらしい$f =も消えて超ハッピー。

無名再帰の活用例

ズンドコキヨシ with PHP - Qiitaを見たら触発されたので書いてみたよ。

<?php
use Teto\Functools as f;
require_once getenv('HOME') . '/.composer/vendor/autoload.php';

call_user_func(f::fix(function ($func) {
    $zundoco = function () {
        static $zundoco = ['ズン', 'ドコ'];
        return $zundoco[mt_rand(0, 1)];
    };

    static $pattern = ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ'];
    return function (array $a) use ($zundoco, $pattern, &$func) {
        $len = count($a);
        if (isset($a[$len - 1])) {
            echo $a[$len - 1], PHP_EOL;
        }
        if ($a === $pattern) {
            echo 'キ・ヨ・シ!', PHP_EOL;
        } else {
            $func(array_merge(
                ((count($pattern) > $len) ? $a : array_slice($a, 1)),
                [$zundoco()]
            ));
        }
    };
}), []);