こんにちは、@ryota-ka です。
僕もそろそろすごいHをたのしく学びたいとか思ってしまうお年頃なのですが、僕みたいなクソザコ初心者プログラマーが使える言語なんてPHPくらいしかないわけですよ。
でも心配は御無用、周囲の人から「PHPer(笑)」とかバカにされているあなたでも大丈夫!今日はみんな大好きPHPを使って関数型プログラミングを勉強していきましょう!
パターンマッチングと再帰
PHPはただの手続き型言語じゃないんだ!!
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php function factorial($n) { switch ($n) { // もしかして: ただの switch case 1: return 1; default: return $n * factorial($n - 1); } } var_dump(factorial(6)); // int(720) |
0とか string とか投げるなよ!!死ぬから!!!
型とか厳しくないしその辺は仕方ないと思う(既に先行きが不安)
カリー化と部分適用, map
ある大学の授業で講師が、素点が60点以上の生徒はそのまま成績として採用し、60点未満の生徒は、ある程度の点数を取っているならば、救済のために60点ちょうどにしてあげたいと考えています。
以下に素点のデータを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php $students = [ [ 'name' => 'Alice', 'mark' => '80', ], [ 'name' => 'Bob', 'mark' => '55', ], [ 'name' => 'Charlie', 'mark' => '70', ], [ 'name' => 'Dave', 'mark' => '40', ], [ 'name' => 'Ellen', 'mark' => '50', ], ]; |
MySQL とかからデータ持ってきたりすると、こんな感じで数字のデータも string で返ってきたりしますよね。つらい。
それはともかく、このままでは Bob と Dave と Ellen が単位を落としてしまいます。救済してあげましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
<?php $curried_min = function($min) { return function ($x) use ($min) { return $x >= $min ? ($x > 60 ? $x : 60) : $x; }; }; $min = $curried_min(50); $rescue = function($student) use($min) { $student['mark'] = $min((int) $student['mark']); return $student; }; $rescued_students = array_map($rescue, $students); var_dump($rescued_students); /* array(5) { [0]=> array(3) { ["id"]=> string(1) "1" ["name"]=> string(5) "Alice" ["mark"]=> int(80) } [1]=> array(3) { ["id"]=> string(1) "2" ["name"]=> string(3) "Bob" ["mark"]=> int(60) } [2]=> array(3) { ["id"]=> string(1) "3" ["name"]=> string(7) "Charlie" ["mark"]=> int(70) } [3]=> array(3) { ["id"]=> string(1) "4" ["name"]=> string(4) "Dave" ["mark"]=> int(40) } [4]=> array(3) { ["id"]=> string(1) "5" ["name"]=> string(5) "Ellen" ["mark"]=> int(60) } } */ |
三項演算子ネストとかしてて最悪だ!!
rescue(55, 50)
とか2変数関数で書いちゃうの、今どきダサいっすよね。というわけで、$min = curried_min(50);
の部分で、カリー化された関数に 50
という引数を部分適用し、点数のフィルターを作っています。こうして無事に数人の学生の単位は救われたのでした。
filter
さて、確かに数人の学生の単位は救われたのですが、結局誰が合格だったのかが一見よくわかりません。array_filter
関数を使って、単位を取得できた学生のみを表示してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<?php $passed_students = array_filter($rescued_students, function($student) { return $student['mark'] >= 60; }); var_dump($passed_students); /* array(4) { [0]=> array(3) { ["id"]=> string(1) "1" ["name"]=> string(5) "Alice" ["mark"]=> int(80) } [1]=> array(3) { ["id"]=> string(1) "2" ["name"]=> string(3) "Bob" ["mark"]=> int(60) } [2]=> array(3) { ["id"]=> string(1) "3" ["name"]=> string(7) "Charlie" ["mark"]=> int(70) } [4]=> array(3) { ["id"]=> string(1) "5" ["name"]=> string(5) "Ellen" ["mark"]=> int(60) } } */ |
一人名前が消えましたね。無残にも単位を鴨川に落としてしまった学生です。
array_map
と array_filter
を組み合わせて、単位を取得できた学生の名前のみを含む配列を取得することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php var_dump(array_map(function($student) { return $student['name']; }, array_filter($rescued_students, function($student) { return $student['mark'] >= 60; }))); /* array(4) { [0]=> string(5) "Alice" [1]=> string(3) "Bob" [2]=> string(7) "Charlie" [4]=> string(5) "Ellen" } */ |
array_map
と array_filter
の引数で配列とクロージャの順番が違うの厳しすぎる。
reduce
最後に、受講者の素点の平均を出したいと思います。まずは各受講者の素点が入った配列を array_map
を使って生成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php $marks = array_map(function($student) { return (int) $student['mark']; }, $students); var_dump($marks); /* array(5) { [0]=> int(80) [1]=> int(55) [2]=> int(70) [3]=> int(40) [4]=> int(50) } */ |
1 2 3 4 5 6 |
<?php $sum = array_reduce($marks, function($x, $y) { return $x + $y; }); var_dump($sum); // int(295) |
array_reduce
を使って合計点を計算できました。array_sum
なんかいらんかったんや!!(Ruby 界隈の方を見ながら)
1 2 |
$average = $sum / count($students); var_dump($average); // int(59) |
こうして平均点が59点だったということが求まりました。便利ですね。
まとめ
素直に他の言語を使った方がいいと思う。酔った勢いで書いたからいろいろ許してほしい。