Closed9

PHP7

ほげさんほげさん

気になったところだけ

fatal error が例外に

fatal error...
あった気がする...

画面が真っ白になるやつだっけ...??

https://qiita.com/mpyw/items/c69da9589e72ceac470c

  • 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が返ってくる

よくあるcmpEQ, LT, GTみたいなやつか

正直戻りがintなのでswitchintで書くことになるなら、ちょっと冴えが足りない気がする

定数作っておけば見通し良いかな?

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);
}

使うかなぁ...??

けど知らないと読めないやつ

ほげさんほげさん

クラスとメソッドの練習

あんまり時間がないので適当に Option, List, Either を作ろうと思う

array_xxx とかは都度調べて行けば大丈夫なはず

ほげさんほげさん
Option.php
<?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));
    }

}
ほげさんほげさん
List.php
<?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));
    }

}
ほげさんほげさん
Either.php
<?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 ? ってどんな感じなんだろ?

なんにせよ、ちょっと慣れてきた

このスクラップは2021/03/31にクローズされました