is_xxx() 関数を使わずに型判定できますか？

# 問題

`\CarrotRakko` という名前空間の直下に以下の8つの関数を定義せよ。定義する関数のシグネチャと外から見た挙動は、グローバル名前空間に存在する同名の関数と同じにすること。

関数/メソッド呼び出しは行なってはならない。ここで、関数/メソッドの呼び出しとはPHP処理系の実装レベルで関数/メソッドがコールされることを指すのであって、PHPコードでの呼び出し方は問わない。なお、以下のソースコードでは関数/メソッド呼び出しを行わずに実装できる部分を、関数/メソッド呼び出しによって簡潔に表現することがある。

実装に用いる言語およびバージョンは **PHP 7.4.x** とする。公式でない処理系や標準でバンドルされていないエクステンション、自作のエクステンションの使用などは認めない。

問1: `is_bool(mixed $var): bool`
問2: `is_int(mixed $var): bool`
問3: `is_float(mixed $var): bool`
問4: `is_string(mixed $var): bool`
問5: `is_array(mixed $var): bool`
問6: `is_object(mixed $var): bool`
問7: `is_resource(mixed $var): bool`
問8: `is_null(mixed $var): bool`

# 問1: `is_bool(mixed $var): bool`

最初に思いついたのは次のような実装です。

```php
<?php

namespace CarrotRakko;

/**
 * boolean型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_bool($var): bool
{
    return ($var === true) || ($var === false);
}
```

**boolean型** には `true` と `false` の2値しかないのでこういうことができます。しかしながら、型の中には取りうる値が有限個ではないもの（ **string型, array型, object型, resource型** ）や、有限個ではあるがリテラルをベタがきするには多すぎるもの（ **int型, float型** ）があるため、**boolean型** の値の種類の少なさに依存しない実装をしてみたいところです。

```php
function is_bool($var): bool
{
    return !!$var === $var;
}
```

ここで [論理否定演算子](https://www.php.net/manual/ja/language.operators.logical.php)（[Logical Not Operator](https://www.php.net/manual/en/language.operators.logical.php)） について確認しておきたいと思います。 `!$var` が評価されるときはまず、 `$var` が **boolean型に変換** されます。変換された結果が `true` であれば `!$var` は `false` に、 `false` であれば `!$var` は `true` に評価されます。

論理否定演算子の確認が済んだところで、 [**booleanへの変換**](https://www.php.net/manual/ja/language.types.boolean.php#language.types.boolean.casting)（[Converting to boolean](https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting)）を理解しましょう。リンク先を読むと、PHPの8つの型はいずれもboolean型に変換できることがわかります。

ここで、 `!` を使って実装した `is_bool(mixed $var): bool` 関数が正しい挙動をすることを証明できるようになりました。 `!!$var` は常にboolean型なので、 `$var` がboolean型ではないときには `!!$var === $var` は `false` に評価されます。 `$var` が `true` のときも `false` のときも `!!$var === $var` は `true` に評価されるので、この関数の挙動が正しいことを証明できました。

`!!` してから元々の値と比較するというような便利なことは他の型ではできそうにないので、もうちょっと一般化しておきましょう。

```php
function is_bool($var): bool
{
    return (bool)$var === $var;
}
```

# 問2: `is_int(mixed $var): bool`

[整数への変換](https://www.php.net/manual/ja/language.types.integer.php#language.types.integer.casting)（[Converting to integer](https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting)）を読むと、boolean型, int型, float型, string型, resource型, null型の6つの型はint型に変換できるようです。array型, object型の2つの型をint型に変換しようとしたときの挙動は未定義と書いてあります。

循環呼び出しにならないように `is_array()` , `is_object()` を定義できると期待すると、次のような実装ができます。

```php
<?php

namespace CarrotRakko;

/**
 * int型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_int($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (int)$var === $var;
}
```

# 問3: `is_float(mixed $var): bool`

[float への変換](https://www.php.net/manual/ja/language.types.float.php#language.types.float.casting)（[Converting to float](https://www.php.net/manual/en/language.types.float.php#language.types.float.casting)）を読むと、float型への変換が定義されている型とint型への変換が定義されている型は同じことがわかります。したがって、 `is_int()` を実装したときと同じ期待のもとで次のような実装が可能です。float型には `NAN` という `NAN !== NAN` な値があることに注意が必要です。

```php
<?php

namespace CarrotRakko;

/**
 * float型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_float($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (
            (float)$var === $var
            || $var !== $var
        );
}
```

# 問4: `is_string(mixed $var): bool`

[文字列への変換](https://www.php.net/manual/ja/language.types.string.php#language.types.string.casting)（[Converting to string](https://www.php.net/manual/en/language.types.string.php#language.types.string.casting)）を読みましょう。array型とobject型があやしいので実験してみます。

```php
(string)[];
// => PHP Notice:  Array to string conversion

(string)(new class() {});
// => PHP Fatal error:  Uncaught Error: Object of class class@anonymous could not be converted to string
```

諦めて `is_int()` , `is_float()` と同様の実装をします。

```php
<?php

namespace CarrotRakko;

/**
 * string型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_string($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (string)$var === $var;
}
```

# 問5: `is_array(mixed $var): bool`

[配列への変換](https://www.php.net/manual/ja/language.types.array.php#language.types.array.casting)（[Converting to array](https://www.php.net/manual/en/language.types.array.php#language.types.array.casting)）を読むと、8つの型はいずれもarray型に変換できるので、次の実装ができます。

```php
<?php

namespace CarrotRakko;

/**
 * array型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_array($var): bool
{
    return (array)$var === $var;
}
```

# 問6: `is_object(mixed $var): bool`

[オブジェクトへの変換](https://www.php.net/manual/ja/language.types.object.php#language.types.object.casting)（[Converting to object](https://www.php.net/manual/en/language.types.object.php#language.types.object.casting)）を読むと、8つの型はいずれもobject型に変換できるので、次の実装ができます。

```php
<?php

namespace CarrotRakko;

/**
 * object型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_object($var): bool
{
    return (object)$var === $var;
}
```

# 問7: `is_resource(mixed $var): bool`

[リソースへの変換](https://www.php.net/manual/ja/language.types.resource.php#language.types.resource.casting)（[Converting to resource](https://www.php.net/manual/en/language.types.resource.php#language.types.resource.casting)）を読むと、resource型に関しては今までと同じ戦略が通用しそうにありません。循環呼び出しにならないように `is_null()` を定義できる自信があるので、次のような実装でゴリ押します。


```php
<?php

namespace CarrotRakko;

/**
 * resource型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_resource($var): bool
{
    return !is_bool($var)
        && !is_int($var)
        && !is_float($var)
        && !is_string($var)
        && !is_array($var)
        && !is_object($var)
        && !is_null($var);
}
```

# 問8: `is_null(mixed $var): bool`

遊ぶ順番を間違えた感...。

```php
<?php

namespace CarrotRakko;

/**
 * null型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_null($var): bool
{
    return null === $var;
}
```

# 展望

関数を実装してみたはいいものの、エラーを吐かないかどうかや、挙動がビルトインの関数と同じかのテストをする必要があるなと思っています。

また、PHPには8つの型を判定する関数以外にも `is_xxx()` な名前の関数があるので、挙動を理解して再発明してみたいですね。

問9: `is_a`
問10: `is_callable`
問11: `is_countable`
問12: `is_finite`
問13: `is_infinite`
問14: `is_iterable`
問15: `is_nan`
問16: `is_numeric`
問17: `is_scalar`
問18: `is_subclass_of`