PHP7
PHP: PHP 5.6.x から PHP 7.0.x への移行 - Manual
気になったところだけ
fatal error が例外に
fatal error...
あった気がする...
画面が真っ白になるやつだっけ...??
- Throwable が最上位
- Throwable は自分で継承できない
- Error はあんまり自分で捕まえない
- Exception を継承したものを自分で投げよう
5 のことはもう忘れちゃった
とりあえず、必要に応じて覚えなすってことにする...
スカラー型宣言 / 戻り値の型宣言
private function div($n, $m)
{
return $n / $m;
}
こんなのがあるとして、よしなに動く
var_dump($this->div(8, 4)); // int(2)
var_dump($this->div(8.0, 4)); // float(2)
引数にint
をつけたり
private function div(int $n, $m)
{
return $n / $m;
}
戻り値にint
をつけたり
private function div($n, $m): int
{
return $n / $m;
}
すると、結果はint(2)
になる
呼べないわけではないのか
と思ったら、デフォルトでは自動変換モードらしい
declare(strict_types=1);
を追加すると
TypeError : Return value of Test::div() must be of the type integer, float returned
怒られた
基本オンで良いと思う
ファイル単位で制御できるって書いてあったけど、php.ini とかで一括設定できる?
配列は?
private function len(array $ns): int
{
return count($ns);
}
var_dump($this->len([1, 2, [8, 9], 3])); // int(4)
array
の次元数とか中身の方は制約できない?
Null 合体演算子
左が null なら右を、ということらしい
var_dump(null ?? 5); // int(5)
var_dump(null ?? null ?? 3); // int(3)
頻出するこれが
isset($obj[$n]) ? $obj[$n] : $default;
こう書ける
$obj[$n] ?? $obj[$n] ?? $default;
ぶっちゃけ生のarray
は色々と使いづらいので、array_xxx
とかは軽く全部ラップした方が良い気がしている...
というか、存在チェック / null チェック多用したくないなぁ...
便利だとは思うけど、頻出させたくはない感じ
宇宙船演算子
1 <=> 1
みたいにすると0, -1, 1
が返ってくる
よくあるcmp
とEQ, LT, GT
みたいなやつか
正直戻りがint
なのでswitch
もint
で書くことになるなら、ちょっと冴えが足りない気がする
定数作っておけば見通し良いかな?
const EQ = 0;
const LT = -1;
const GT = 1;
private function cmp($n, $m): string
{
switch ($n <=> $m) {
case EQ:
return "equals";
case LT:
return "less than";
case GT:
return "greater than";
default:
return "no match";
}
}
var_dump($this->cmp(3, 5)); // less than
多用するかな?
けど知らないとビビりそうだったので知れて良かった
match
が式なのか!
と思ってテンション上がったけど、php8 からだったー残念
use 宣言のグループ化
use core\user\{Id, Name, Status};
みたいにできる
多分手で書かないしリンターをちゃんとしていれば意識はしないと思う
ジェネレータの委譲
ジェネレータの中から他のジェネレータを続けられる的な
private function gen1()
{
yield 1;
yield 2;
yield from $this->gen2();
}
private function gen2()
{
yield 3;
yield 4;
}
foreach ($this->gen1() as $n) {
var_dump($n);
}
使うかなぁ...??
けど知らないと読めないやつ
7.4 からアロー関数が使えるらしい
PHP: アロー関数 - Manual
クラスとメソッドの練習
あんまり時間がないので適当に Option, List, Either を作ろうと思う
array_xxx とかは都度調べて行けば大丈夫なはず
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
interface Option
{
public function map($f): Option;
public function filter($p): Option;
public function flatMap($f): Option;
}
class Some implements Option
{
private $v;
/**
* @param mixed $v
*/
public function __construct($v)
{
$this->v = $v;
}
public function map($f): Option
{
return new Some(call_user_func($f, $this->v));
}
public function filter($p): Option
{
return call_user_func($p, $this->v) ? new Some($this->v) : new None();
}
public function flatMap($f): Option
{
return call_user_func($f, $this->v);
}
}
class None implements Option
{
public function map($f): Option
{
return new None();
}
public function filter($p): Option
{
return new None();
}
public function flatMap($f): Option
{
return new None();
}
}
class OptionTest extends TestCase
{
/**
* @test
*/
public function some_map()
{
$org = new Some(5);
$exp = new Some(7);
$this->assertEquals($exp, $org->map(function($n) { return $n + 2;}));
}
/**
* @test
*/
public function some_filter()
{
$org1 = new Some(6);
$exp1 = new Some(6);
$this->assertEquals($exp1, $org1->filter(function($n) { return $n % 2 == 0;}));
$org2 = new Some(5);
$exp2 = new None();
$this->assertEquals($exp2, $org2->filter(function($n) { return $n % 2 == 0;}));
}
/**
* @test
*/
public function some_flatMap()
{
$half = function($n) { return $n % 2 == 0 ? new Some($n / 2) : new None(); };
$org1 = new Some(6);
$exp1 = new Some(3);
$this->assertEquals($exp1, $org1->flatMap($half));
$org2 = new Some(5);
$exp2 = new None;
$this->assertEquals($exp2, $org2->flatMap($half));
}
}
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
abstract class MyList // なんか list() という文があったので衝突回避
{
abstract public function map($f): MyList;
abstract public function filter($p): MyList;
abstract public function flatMap($f): MyList;
abstract public function prepend($v): MyList;
public static function of(array $vs):MyList // note2: java みたいに interface では body を持てない
{
if (count($vs) == 0) {
return new Nil();
} else {
$v = array_shift($vs);
return new Cons($v, self::of($vs));
}
}
}
class Cons extends MyList
{
private $head;
private $tail;
/**
* @param mixed $head
* @param MyList $tail
*/
public function __construct($head, MyList $tail)
{
$this->head = $head;
$this->tail = $tail;
}
public function map($f): MyList
{
return new Cons(call_user_func($f, $this->head), $this->tail->map($f));
}
public function filter($p): MyList
{
if (call_user_func($p, $this->head)) {
return new Cons($this->head, $this->tail->filter($p));
} else {
return $this->tail->filter($p);
}
}
public function flatMap($f): MyList
{
$acc = new Nil();
foreach (call_user_func($f, $this->head) as $v) {
$acc = $acc->prepend($v);
}
return self::rec($f, $this->tail, $acc);
}
private static function rec($f, MyList $l, MyList $acc): MyList
{
if ($l instanceof Nil) {
return self::reverse($acc, new Nil());
} else {
$tail = $acc;
foreach (call_user_func($f, $l->head) as $v) { // note1: 自作の型はキャストできないので警告あり
$tail = $tail->prepend($v);
}
return self::rec($f, $l->tail, $tail); // note1
}
}
private static function reverse(MyList $l, MyList $acc): MyList
{
if ($l instanceof Nil) {
return $acc;
} else {
$acc = $acc->prepend($l->head); // note1
return self::reverse($l->tail, $acc); // note1
}
}
public function prepend($v): MyList
{
return new Cons($v, $this);
}
}
class Nil extends MyList
{
public function map($f): MyList
{
return new Nil();
}
public function filter($p): MyList
{
return new Nil();
}
public function flatMap($f): MyList
{
return new Nil();
}
public function prepend($v): MyList
{
return new Cons($v, $this);
}
}
class MyListTest extends TestCase
{
/**
* @test
*/
public function cons_map()
{
$org = MyList::of([1, 2, 3, 4]);
$exp = MyList::of([3, 4, 5, 6]);
$this->assertEquals($exp, $org->map(function($n) { return $n + 2;}));
}
/**
* @test
*/
public function cons_filter()
{
$org = MyList::of([1, 2, 3, 4]);
$exp = MyList::of([2, 4]);
$this->assertEquals($exp, $org->filter(function($n) { return $n % 2 == 0;}));
}
/**
* @test
*/
public function cons_flatMap()
{
$triple = function($n) { return [$n, $n + 1, $n + 2]; };
$org = MyList::of([1, 2, 3]);
$exp = MyList::of([1, 2, 3, 2, 3, 4, 3, 4, 5]);
$this->assertEquals($exp, $org->flatMap($triple));
}
}
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
abstract class MyEither // note3: Either だと PHPUnit が No tests executed! になる...なんで...??
{
abstract public function map($f): MyEither;
abstract public function flatMap($f): MyEither;
public static function ap(MyEither $e1, MyEither $e2, MyEither $e3, $f): MyEither
{
return $e1->flatMap(
function($v1) use ($e2, $e3, $f) { return $e2->flatMap(
function($v2) use ($v1, $e3, $f) { return $e3->map(
function($v3) use ($v1, $v2, $f) { return call_user_func($f, $v1, $v2, $v3); }
); }
); }
);
}
}
class Right extends MyEither
{
private $v;
/**
* @param mixed $v
*/
public function __construct($v)
{
$this->v = $v;
}
public function map($f): MyEither
{
return new Right(call_user_func($f, $this->v));
}
public function flatMap($f): MyEither
{
return call_user_func($f, $this->v);
}
}
class Left extends MyEither
{
private $v;
/**
* @param mixed $v
*/
public function __construct($v)
{
$this->v = $v;
}
public function map($f): MyEither
{
return new Left($this->v);
}
public function flatMap($f): MyEither
{
return new Left($this->v);
}
}
class MyEitherTest extends TestCase
{
/**
* @test
*/
public function right_map()
{
$org = new Right(5);
$exp = new Right(7);
$this->assertEquals($exp, $org->map(function($n) { return $n + 2;}));
}
/**
* @test
*/
public function right_flatMap()
{
$half = function($n) { return $n % 2 == 0 ? new Right($n / 2) : new Left("not even"); };
$org1 = new Right(6);
$exp1 = new Right(3);
$this->assertEquals($exp1, $org1->flatMap($half));
$org2 = new Right(5);
$exp2 = new Left("not even");
$this->assertEquals($exp2, $org2->flatMap($half));
$org3 = new Right(8);
$exp3 = new Right(2);
$this->assertEquals($exp3, $org3->flatMap($half)->flatMap($half));
$org4 = new Right(6);
$exp4 = new Left("not even");
$this->assertEquals($exp4, $org4->flatMap($half)->flatMap($half));
}
/**
* @test
*/
public function ap()
{
$addAll = function($v1, $v2, $v3) { return $v1 + $v2 + $v3; };
$e1 = new Right(1);
$e2 = new Right(3);
$e3 = new Right(5);
$exp = new Right(9);
$this->assertEquals(MyEither::ap($e1, $e2, $e3, $addAll), $exp);
$e4 = new Right(1);
$e5 = new Left("invalid number format");
$e6 = new Right(5);
$exp = new Left("invalid number format");
$this->assertEquals(MyEither::ap($e4, $e5, $e6, $addAll), $exp);
$e7 = new Left("too big");
$e8 = new Right(3);
$e9 = new Left("invalid number format");
$exp = new Left("too big");
$this->assertEquals(MyEither::ap($e7, $e8, $e9, $addAll), $exp);
}
}
- namespace / use がうまく動かなかった
- 素のプロジェクトに phpunit.phar 置いて動かしてるだけなので、今はこだわり過ぎないことにした
- ジェネリクスと無名関数の型定義がないとものすごいあひるタイピング 🦆
-
ap
とかは上手に使えばドメイン層とかで重宝するはずなんだけど、どこまで実用に耐えられるのかなー - アロー関数使いたいなー
- そういうのってあんまり使わないのかな
- サーバサイド PHP ? ってどんな感じなんだろ?
なんにせよ、ちょっと慣れてきた
やっつけ感すごい
あとはお仕事で頑張る