「Goはダックタイピングだよ」と言われるケースをちょくちょく見かけたものの、本当にそうなの?となっていたので調べた。ここらへん、実はPHPがとても良い例になりそうなので、GoとPHPのコード例を出していきます。
Nominal Typing
まず一番わかりやすいものから。その型であるとするには、「その型だよ」と宣言する必要があるというもの。PHPやJavaが採用している。
例えば、「にゃーんと鳴く」という猫インターフェースを定義する。このとき、ツイッターで「にゃーん」と鳴いているエンジニアおじさんを区別できるのが Nominal Typing 。
PHP は Nominal Typing を採用している。
<?php // Iterator型と認識される class CharacterIterator implements Iterator { // ... } // Iterator型と認識されない class CountIterator { // ... }
Go は 後述する Structual Typing を採用しているので、Noming Typing ではない認識。
// Stringer を宣言していないが Stringer である type MyULID struct { // ... } func (u MyULID) String() string { // ... }
structural typing
その型がインターフェースの形をしていれば、そのインターフェースを満たしていると見做すもの。GoやTypeScriptが採用している。
例えば、「にゃーんと鳴く」という猫インターフェースを定義する。このとき、Structural Typing ではツイッターで「にゃーん」と鳴いているエンジニアおじさんも猫であると見做す。
Goは structural typing を採用している。上述のコードを再掲。
// Stringer を宣言していないが Stringer である type MyULID struct { // ... } func (u MyULID) String() string { // ... }
PHP は structural typing は採用していない。以下のコードの MyULID は toInt() を実装しているが、 Intable とは見做されない。
<?php interface Intable { function toInt(): int; } // MyULID は Intable ではない class MyULID { // ... public function toInt(): int { // ... } } function output(Intable $i) { echo $i->toInt(); } $id = new MyULID(); output($id); // 型エラー
duck typing
変数や値がそれを呼び出し可能であれば、それを呼び出せるというもの。PHPやRubyが採用している。
例えば、$cat に meow() というメソッドがあれば、meow()を呼び出せるというもの。
PHPは duck typing を採用している。下のコードは mixed 型の変数から meow() を呼び出しているが、meow()が存在していれば実行できるし、存在していなければエラーとなる。だが、少なくとも型チェックは通過する。
<?php function getMeowable(): mixed { // ... } $meowable = getMeowable(); $meowable->meow();
Go は duck typing ではない認識。以下のコードは meowable が Meow() を実装しているにも関わらず、コンパイルエラーとなる。
type Meowable interface { Meow() } // Cat は Meowable type Cat struct{} func (c Cat) Meow() { // ... } func getMeowable() any { return Cat{} } func main() { meowable := getMeowable() meowable.Meow() // コンパイルエラー }
まとめ
| typing | PHP | Go |
|---|---|---|
| nominal | o | x |
| structural | x | o |
| duck | o | x |
nominal typing であり、 duck typing である PHP が少し異例な気がしてきた。