PHP触ったことのないプログラマーが動的な型付けに頭を抱えた話


こんにちは!5月に入社した黒羽(くろは)です!


隔週での更新を目標に開発一同で再開しましたテックブログ、再開後としては2つ目(?)の記事になります。
いきなり社歴の浅い新人が書いているので驚かれる方もいると信じて簡単な自己紹介をしつつ、本題に入っていこうと思います。 
さて、文系の学部を出てプログラマーになって6年目も中盤に差し掛かっておりますが、大学在学中よりも時間の経過が速かった気がします。
今までに取り扱った言語としては以下のような感じです。
  • Java
  • C#
  • Python
  • javascript
  • CSS
  • HTML
  • その他にコマンドプロンプト(世の人々がプログラムと聞いてイメージするであろう『黒い画面に文字がダーッとでる』やつ)で使用するスクリプト類など…
  最後のは言語じゃなくてコマンドの塊じゃん!と言われそう イメージマジックの開発で主に使われている言語はPHPなので入社して初めてPHPを触りました。
今まで触ってきた言語とは書き味や仕様が違っていて日々調べながら習得を進めています。
今日はそんな言語仕様に振り回されてハマってしまった時の原因について調べたので(社内ではそりゃ当たり前だろといわれそうなので未来のPHP初学者に向けて)共有できればと思います。

きっかけ


イメージマジックの各プロジェクトのPHPのソースコードを眺めているとどのソースにも
declare(strict_types=1);
と出てきます。 これは何ぞやと調べてみたところ次の記事に行き当たりました。
この記事の冒頭でこのように記述されていました。
 
 
declare(strict_types=1); とは、PHP7から導入された、厳格な型検査モードの指定構文です。

厳格な型検査モード・・・・?????と思いつつ読み進めてみると更に以下の記述が。
<?php
function add(int $a, int $b): int
{
    return $a + $b;
}

var_dump(add(1.0, 2.0));
この状態で単体実行すると、int(3)が出力されます。
  この時の私の頭の中では1つの考えに支配されていました。
「int型の変数にdouble型を堂々と代入するな!!!!!!」


言語による型付け


あまりに理解ができなくて大声になりましたが気を取り直していきます。
プログラミング言語には開発者が変数や戻り値になど事前にどういう型のデータが入るかを指定する静的な型付けと実行時にコンパイラやインタプリンタがコードを解釈してよしなにデータ型を判断してくれる動的型付けがあります。
PHPは言語仕様として動的な型付けを行っています。
私はJava()静的な型付け)からプログラミングをはじめて、当時の学習用テキストにもデータ型が一致しないとエラー吐くと脅されて教えられました。
そのため、データ型が定義されている変数に異なるデータ型を突っ込まれると困ってしまうわけです。
しかもdouble同士の加算でint型で返ってくるとはこれ如何に。
上記のサンプル関数では$a=1.0≒1(int型)、$b=2.0≒2(int型)としてPHPの方で判断しているようです。
話は逸れましたがdeclare(strict_types=1);を付けてさきほどの関数を実行すると

PHP Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, float given, called in /Users/hiraku/sandbox/stricttypes/A.php on line 9 and defined in /Users/hiraku/sandbox/stricttypes/A.php:4 Stack trace: #0 /Users/hiraku/sandbox/stricttypes/A.php(9): add(1, 2) #1 {main} thrown in /Users/hiraku/sandbox/stricttypes/A.php on line 4

とエラーが発生します。
つまりdeclare(strict_types=1);とは動的な型付けをしているPHPで静的な型付けのように振舞わせる宣言という風に解釈できます。
ちなみにこの宣言をしているファイルを呼び出している場合のみ有効であるため、別ファイルからuse文等で参照して同じように関数を呼び出しても緩い型検査のままであるのでint型で返ってきます。
つまり引数を$a=1.0,$b=2.0とする場合、
  • declare(strict_types=1); を宣言しているファイルAのadd関数を呼び出す⇒Typeエラーが発生
  • declare(strict_types=1); を宣言しているファイルAを参照しているがdeclare(strict_types=1); を宣言していないファイルBでファイルAのadd関数を呼び出す⇒int型の戻り値が返却される。 
ということです。

最後に


つまるところ何が言いたかったのかというとカルチャーショックを受けたという話でした。
動的な型付けは手軽な一方で想定していない値が返ってくる可能性があるのでやはり多少面倒でも静的な型付けできっちりとデータ型を指定して書く方が私は好きです。(というかぱっと見で何の値が入ってくるのかソースから読み取れないと後々困ることもある)
最後に余談となりますがこの記事を書いてる間に裏どりがてらに色々調べていたのですが 「動的な型付け」と「型推論」は同じものだと思っていましたが違うもののようです。
ここら辺も少し調べてみようと思います。
ここまで読んでいただきありがとうございました。何かの参考になれば幸いです!