PHPの小ネタ: タイプヒンティングの罠
タイプヒンティングとは
PHP5からタイプヒンティングという似非タイプチェック機能が使えるようになっています。
<?php interface Runnable { public function run(); } class Foo implements Runnable { public function run() { echo 'Foo', "\n"; } } class Bar { public function run() { echo 'Bar', "\n"; } } function run(Runnable $runner) { $runner->run(); } run(new Foo); run(new Bar);
Foo PHP Catchable fatal error: Argument 1 passed to run() must implement interface Runnable, instance of Bar given
なるほど中々悪くなさそうな機能です。 が、このタイプヒンティングという機能には、色々と罠が潜んでいます。
スカラー型には使えない
ちゃんとマニュアルを読みましょうという話ですが、
タイプヒントは int や string といったスカラー型には使えません。 また、リソース や トレイト も使えません。
と書かれています。
<?php function add(int $a, int $b) // NG { return $a + $b; }
ついついやりたくなるんですけどね…。
nullは渡せない、が渡せないこともない
何言っているのかというと、こういうことです。
<?php function echoTimestamp(DateTime $dt) { echo ($dt ? $dt->getTimestamp() : 0), "\n"; } echoTimestamp(null);
PHP Catchable fatal error: Argument 1 passed to echoTimestamp() must be an instance of DateTime, null given,
なるほど、nullは渡せないのか! と思いきや。
<?php function echoTimestamp(DateTime $dt = null) { echo ($dt ? $dt->getTimestamp() : 0), "\n"; } echoTimestamp(null);
0
デフォルト引数にnullを与えている場合は、null値を引数に渡せます。なんじゃそりゃ。
しかし、デフォルトのパラメータの値として NULL を使用した場合は、後から任意の値を引数に指定できるようになります。
デフォルト引数が無いときにnull値を与えられないというのは、メソッド内部でnullかどうかで分岐してないということが保証されるので、それはそれで良いのかな*1と良心的に解釈できますが、デフォルト引数nullの仕様は完全に蛇足な気がします。
<?php function addSeconds(DateTime $dt = null, $seconds = 0) { if (is_null($dt)) { $dt = new DateTime('now'); } return $dt->add(new DateInterval("PT{$seconds}S")); } $dt = new DateTime('now'); echo $dt->format('H:i:s'), "\n"; echo addSeconds($dt, 300)->format('H:i:s'), "\n"; echo addSeconds(null, 300)->format('H:i:s'), "\n";
全部にデフォルト引数与えるの、なんか違わない?引数なしで呼べるけど意図した使い方じゃないよね、という…。
所感
タイプヒンティングは、nullが来ないことを保証されているのだとポジティブに考えて、上手に使うのが良いのかと思います。
hacklangではもっと厳密に型を宣言できるようなので、一度試してみたい所です。
structの関数プロパティとメソッドの挙動
package main import "fmt" type OpFunc func(int, int) int type Operation struct { // associateされていないのでOperationのプロパティ・メソッドへのアクセスは出来ない。 Do1 OpFunc // 以下コメントを外すと "type Operation has both field and method named Do2" //Do2 OpFunc } func (op Operation) Do2(a, b int) int { return op.Do1(a, b) } func main() { op := &Operation{ Do1: func(a, b int) int { return a + b }, } fmt.Println(op.Do1(1, 2), op.Do2(3, 4)) }
golangのType Assertionメモ
- Type AssertionはC++のdynamic_cast的な機能。
- interfaceを別の型にキャストする時に使用。
- 2通りの受け方がある。
package main import ( "fmt" "errors" ) type MyError struct { i int } func (e *MyError) Error() string { return fmt.Sprintf("i = %d", e.i) } func check1(e error) { myErr, ok := e.(*MyError) if ok { fmt.Println(myErr) } else { fmt.Println("It's not *MyError") } } func check2(e error) { myErr := e.(*MyError) if myErr != nil { fmt.Println(myErr) } else { fmt.Println("It's not *MyError") } } func main() { e100 := &MyError{100} e200 := errors.New("200") check1(e100) check1(e200) check2(e100) check2(e200) // panic }
http://play.golang.org/p/MY2G3J_QPU
package main import ( "fmt" "strconv" ) func Println(x interface{}) { if i, ok := x.(int); ok { fmt.Println(strconv.Itoa(i)) return } if i, ok := x.(int64); ok { fmt.Println(strconv.FormatInt(i, 10)) return } if _, ok := x.(string); ok { fmt.Println("ニヤ(・∀・)ニヤ") return } } func main() { Println(int(100)) Println(int64(200)) Println("300") }