array_mapにありがとう、さよなら

このスライドは[ヤパチーエイジアハチオウジ2016](http://yapcasia8oji-2016mid.hachiojipm.org/)の「**[WIP]あなたがエンタープライズファンクショナルPHPライブラリ[Teto\Functools](https://github.com/BaguettePHP/Functools/)を採用しなければならない11個の理由**」の発表内容です。

### 関連記事

- [Functoolsを作った](http://qiita.com/tadsan/items/abc52f0739ab5b4e781b)
- [過剰にスマートなPHPを書こう (無名再帰篇)](http://qiita.com/tadsan/items/6b8cc35ff9ccb3c27678)

-----

## お前誰よ

* うさみけんた <sub>ぞぬえぐぜ/っどさん</sub>
  * GitHub: `zonuexe` (Pakagistも同じ)
  * Twitter: `@tadsan`
* ピクシブ株式会社でpixivってサービスやってる
* 最近はPHPの静的解析がアツい

-----

# さて

-----

## Twitterにて<br>2016年5月25日

-----

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">あまり有名ではないのでもしかしたらご存じないかもしれませんが、PHPには高速高機能なforeachってイテレーション機構が言語組み込みにあるので、わざわざarray_mapとか書かなくてもいいんですよ！！！！！</p>&mdash; うさみけんた (@tadsan) <a href="https://twitter.com/tadsan/status/735444175236124672">May 25, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

-----

<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="ja" dir="ltr">まじめなはなし、array_mapは名前の通り配列しかサポートしてくれないので本当に不必要でつらい。 <a href="https://t.co/lO7UCVlW9l">https://t.co/lO7UCVlW9l</a></p>&mdash; うさみけんた (@tadsan) <a href="https://twitter.com/tadsan/status/735447161840668672">May 25, 2016</a></blockquote>

-----

<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="ja" dir="ltr">array_mapが活きるパターンもがんばって挙げればなくはなくて、ひとつは array_map(&#39;floatval&#39;, range(1, 5) 、もうひとつは array_map(null, range(&#39;a&#39;, &#39;e&#39;) ,range(1, 5)) 。以上終了でございます。</p>&mdash; うさみけんた (@tadsan) <a href="https://twitter.com/tadsan/status/735448020997656576">May 25, 2016</a></blockquote>

-----

<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="ja" dir="ltr">array_mapに渡していいのはcallableな値のみで、 array_map(function(){ ... とか書き始めたらそれは敗北フラグ。素直に foreach で書け！</p>&mdash; うさみけんた (@tadsan) <a href="https://twitter.com/tadsan/status/735448356521054209">May 25, 2016</a></blockquote>

-----

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">僕ずっと自分のことをクロージャ厨だと思ってたけど動的スコープのEmacs Lisp最高！ とか叫んだりPHPでarray_map(function()... って書いたら負けフラグだと主張したりするし、自分で認識してるほどそうではないのかもしれない。</p>&mdash; うさみけんた (@tadsan) <a href="https://twitter.com/tadsan/status/735452489961480192">May 25, 2016</a></blockquote>

-----

みたいなことを、ゆっくり30分かけて話します。

<br>

# 三三 ヾ(〃＞＜)ﾉﾞ 　　... λ

-----

## おことわり

この発表の内容で出てくる用語などは厳密なものではなく、現代的な解釈を反映したものではない場合があります。

つまり適当にWikipediaを流し読んで、難しそうな部分を都合良く読み飛ばしたような感じです。

-----

## おことわり

予防線を張っておきますが筆者は本発表で触れるいろんな概念について専門教育を受けたり体系的な研究に取り組んだことがあるわけではないので、こと数学的な概念への言及は疑ってかかってくださいね。「Haskellの機能をコピーした」ってレベルでは理解してるけどHaskellerじゃないよ。

-----

## アジェンダ

 1. 函数ってなんだっけ
 2. PHPのイテレータ
 3. Teto\Functoolsの紹介

-----

## 函数とは何か

- **函数**と**関数**の意味は同じ、特別な含意はない
- 中学校で $f(x) = 3x + 2$ みたいなのを習った
- PHPに翻訳すると次のように書ける

```php
function f($x) { return 3 * $x + 2; }

echo "f(3) = ", f(3), PHP_EOL;
```

-----

## 函数とは何か

- 算数でならった**公式**のようなものは、<br>プログラミング言語の函数で表現できる

```php
/** 円の面積 (πr²) */
function circle_area($r) { return M_PI * $r ** 2; }

echo "半径5: 面積=", circle_area(5), PHP_EOL;
// 半径5: 面積=78.539816339745
```
-----

## 函数とは何か

- 用語をひとつだけ覚えてください
- 「**適用**」 (apply)
  - $f(a)$: 「$a$ に $f$ を適用する」
  - PHPの **函数呼び出し** みたいな感じ
- いちいち「函数呼び出しする」はめんどいから<br>今日のところは「適用する」と呼ぶ

-----

## PHPでの「函数」

- 函数(文): `'f'`
    - `function f($a){ ... }`
- メソッド： `[$obj, 'f']`
    - クラス内 `public function f($a){}`
- 静的メソッド： `'Klass::f` / `['Klass', 'f']`
    - クラス内 `public function f($a){ ... }`
- 函数式： ローカル変数に代入可能
    - `$f = function($a) { ... };`

-----

## PHPでの「函数」 = `callable`

全ての函数は[変数代入して利用](https://ja.wikipedia.org/wiki/%E7%AC%AC%E4%B8%80%E7%B4%9A%E9%96%A2%E6%95%B0)できる。

```php
$f = 'f';         // 関数の f()
$f = [$obj, 'f']; // メソッド $obj->f()
$f = 'Klass::f';  // 静的メソッド Klass::f()

$f($v);
array_map($f, range(1, 10));
```

まとめて`callable`と呼ぶ (タイプヒント可能！)

-----

## 集合

- 難しい概念はおいといて
- なんか物が集まったやつ
- PHPの配列でそれっぽく表現できる

```php
$fruits = ["りんご", "みかん", "ばなな"];
```

- 集合に含まれるものを「**要素**」(element)と呼ぶ
- 「配列の中身」のことも同じように要素と呼ぶ

-----

## お約束

- ここでは「同じ引数をなら同じ値が返る」<br>函数は「同じ」だってことにします
    - PHPでの一般的な解釈ではありません
- 中身でどんな処理をしようが「同じ」です
- これを「函数の外延的等値性」と呼ぶらしい

-----

## 「同じ函数」の比較

どう書いても結果は同じなので等値。

```php
function pow3($n) { return $n ** 3; }
function pow3($n) { return $n * $n * $n; }
function pow3($n) {
   $t = $n;
   for ($i = 0; $i < (3 - 1); $i++) {
       $t *= $n;
   }

   return $t;
}
```

-----

### 余談

「外延的等値性」なんて用語を持ち出すのが妥当かどうかはさておき、概念はとても重要です。「チューニング」や「リファクタリング」とは、**出力を変化させることなく**(*あるいは想定内に収め*)実行効率やコードの読みやすさの改善を図る技法だからです。ユニットテストは入出力が期待通りであることを検証するための良い手法です。

-----

### さらに余談

*これはほんと余談なのだけれど「外延(extension)」の反対語は「内包(intension)」です（[内包と外延](https://ja.wikipedia.org/wiki/%E5%86%85%E5%8C%85%E3%81%A8%E5%A4%96%E5%BB%B6)）。Pythonを知ってる型は「内包表記([List comprehension](https://en.wikipedia.org/wiki/List_comprehension))」を書いたことがあるかもしれませんね。集合の外延的表記とは、配列リテラルのように`[1, 2, 3]`と要素をひとつひとつ並べ上げる書きかたのことです。*

-----

## map

-----

## map

- 集合っぽいものの要素に対して、<br>それぞれに函数を適用した集合にするのがmap
- 集合論の用語では[写像](https://ja.wikipedia.org/wiki/%E5%86%99%E5%83%8F)

-----

## map

```php
function x2($n) { return $n * 2; }
```

| orig | 1 | 2 | 3 | 5 | 7 | 13 |
|:---:|:-:|:-:|:-:|:-:|:-:|:--:|
| **new** | 2 | 4 | 6 | 10 | 14 | 26 |

「集合っぽいもの」を表にしてみる。

-----

## 「集合」っぽいもの

- 配列
  - 要素の重複を考慮する必要あり
- 配列のキー
  - 重複はなし、ただし文字列と整数に限る
- ジェネレータ
  - 割と最近(PHP 5.5, 2013年)に入った

-----

## どうするか

- PHP標準機能
  - `foreach`でループを回す
  - `array_map`で配列要素に適用する

-----

## foreach

```php
function x2($n) { return $n * 2; }

$orig = [1, 2, 3, 5, 7, 13];
$new = [];
foreach ($orig as $o) {
    $new[] = x2($o);
}
```

外延的等値性の話をしたので立派にこれもmap

-----

## array_map

```php
function x2($n) { return $n * 2; }

$orig = [1, 2, 3, 5, 7, 13];
$new = array_map('x2', $orig);
```

- ちっちゃい配列なら別にこれでいい
- 長大な配列で最終的に全ての要素を利用しない場合、無駄な処理が発生する

-----

## 何が問題なのか

- 標準のmap函数は`array_map`しかない！
- `foreach`で書くと一時変数が必要だし
- `array_map`は配列にしか利用できない
    - `Traversable`クラス
    - ジェネレータ
- `function`とかいちいち入力するのはだるい

-----

### 解決する手段

 1. イテレータライブラリを導入する
 2. Teto\Functoolsを導入する

-----

## イテレータライブラリ

- [`nikic\iter`](https://github.com/nikic/iter)
    - 単純なイテレータじゃない機能も入ってる
- [Underbar.php](http://emonkak.github.io/underbar.php/)
- [Ginq](https://github.com/akanehara/ginq)

これらのライブラリには処理を遅延評価(必要になるまで計算しない)機能が含まれている。

-----

## べつに自作してもいい

巨人の肩に乗った方が良いので推奨もしない

```php
<?php /* tadsan@zonu.me | license WTFPL */

/**
 * @param  array|\Traversable $iter
 * @param  callable           $callback
 * @return \Generator
 */
function map($iter, callable $callback)
{
    foreach ($iter as $k => $v) {
        yield $k => $callback($v);
    }
}

/**
 * @param  array|\Traversable $iter
 * @param  callable           $callback
 * @return \Generator
 */
function map_kv($iter, callable $callback)
{
    foreach ($iter as $k => $v) {
        yield $k => $callback($k, $v);
    }
}

/**
 * @param  array|\Traversable $iter
 * @return array
 */
function to_array($iter)
{
    if (is_array($iter)) {
        return $iter;
    }

    $ary = [];
    foreach ($iter as $i => $v) {
        $ary[$i] = $v;
    }

    return $ary;
}

/**
 * @param  array|\Traversable $iter
 * @param  int                $n
 * @return \Generator
 */
function take($iter, $n)
{
    $i = 0;
    foreach ($iter as $x) {
        if ($n <= $i++) { return; }
        yield $x;
    }
}
```

-----

## イテレータだけでは解決しない問題

- `function`って打つのはやっぱりだるい！！

-----

### ここでやっと本来のタイトルの提案

-----

# あなたがエンタープライズファンクショナルPHPライブラリTeto\Functoolsを採用しなければならないn個の理由

-----

## Functoolsの機能

- 部分適用
- 演算子ラッパー
- ソート函数ラッパー
- 無名再帰
- メモ化
- 函数合成 / パイプ

-----

## Functoolsのもくろみ

mapを利用するときに全力で`function`って書かないようにする！

-----

### インストール

```
composer require zonuexe/functools
```

Composerを読み込む

```php
<?php
include_once __DIR__ . '/vendor/autoload.php';
```

-----

### 基本

PHPスクリプトの先頭の方でクラスをインポートする。

```php
<?php

use Teto\Functools as f;
```

-----

### 部分適用

既存の函数に引数を当てはめた状態の函数を作る

```php
implode(",", range(1, 10));
// "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"

$comma = f::partial("implode", [", "]);
$comma(range(1, 10));
// "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"
```

-----

### 演算子ラッパー

```php
2 + 3;

$add = f::op("+");
$add(2, 3); // (2 + 3) === 5

// 同時に部分適用もできる
$add_1 = f::op("+", [1]);
$add_1(4);  // (1 + 4) === 5
```
-----

### 言語構造ラッパー

`include`や`print`は函数じゃないのでサポート。

```php
array_map(f::op('include_once'), $files);
array_map(f::op('eval'), $codes);
```

↑ いろいろ危ないかもしれないので推奨はしない

-----

### 言語構造ラッパー

```php
// Before
$is_odd = function ($n) { return $n % 2 == 1; };
$odd_even = function ($n) use ($is_odd) {
    return sprintf("%d: %s", $n, $is_odd($n) ? '奇数' : '偶数');
};
// After
$is_odd = f::compose(f::op('%', [1 => 2], 0), f::op('==', [1 => 1], 0));
$odd_even = f::op('if', [$is_odd, f::partial('sprintf', ['%d 奇数'], 1), f::partial('sprintf', ['%d 偶数'], 1)]);

array_map($odd_even, range(1, 10));
```

どっちが読みやすいかはご自身で判断をry

-----

### sortラッパー

配列ソート函数は仕様上`map`できないので、<br>いい感じにラップしてる。

```php
array_map(f::op('ksort'), $arrays);
array_map(f::op('sort', [1 => SORT_NUMERIC]), $arrays);
```

-----

### 無名再帰 (fix)

Haskellの同名の函数をコピーした。
[Haskell/不動点と再帰 - Wikibooks](https://ja.wikibooks.org/wiki/Haskell/%E4%B8%8D%E5%8B%95%E7%82%B9%E3%81%A8%E5%86%8D%E5%B8%B0)

```php
$fib = f::fix(function ($fib) {
    return function ($x) use ($fib) {
        return ($x < 2) ? 1 :
            $fib($x - 1) + $fib($x -2);
    };
});

$fib(6); // 13
```

-----

### タプル (固定個の値の組)

任意のオブジェクトをキーにできる。


```php
$teto = f::tuple('：name',"Teto Kasane",'：age',31,'：birth',"2008-04-01",'：item',"Baguette");
$teto->pget('：name');  // "Teto Kasane"
$teto->pget('：age');   // 31
$teto->pget('：birth'); // "2008-04-01"
$teto->pget('：item');  // "Baguette"
```

内部構造はimmutableなセルによる連結リスト

-----

### カリー化

PHPでカリー化が適切な場面は皆無(断言)

-----

### メモ化

漸化式になるような単純な再帰函数を効率よくメモ化する

```php
// simple fibonacci function. But, very very slow.
$fib1 = function ($n) use (&$fib1) {
    return ($n < 2) ? $n : $fib1($n - 1) + $fib1($n - 2);
};

// simple fibonacci function too. very fast!
$fib2 = f::memoize(function ($n) use (&$fib2) {
    return ($n < 2) ? $n : $fib2($n - 1) + $fib2($n - 2);
}, /*initialize=*/ [0, 1]);
```

-----

### `composer require zonuexe/functools`

-----

### いますぐインストール