はじめに
オブジェクト指向プログラミングの学習を進める中で、「ダックタイピング」について腹落ちするまでにかなり遠回りしてしまったので、それをまとめて記事にしました。
そもそもダックダイピングを理解するためには「 動的型付け言語 」の知識だけでなく、「 静的型付け言語 」の知識も必要でした。
筆者と同じように Ruby や Python などの動的型付け言語から学習して、静的型付け言語についてはまったく手をつけてこなかった初学者はそこそこ多いと思います。
ぜひこの記事でなんとなく理解した気になりましょう!( ̄▽ ̄)
そもそもダックタイピングとは?
ダックタイピング(Duck typing)とは、オブジェクトの 型(クラス)を明示的に宣言せずに 、オブジェクトの振る舞い(メソッド)やプロパティを利用することで、そのオブジェクトの型(クラス)を推測する手法です。この概念は、特に 動的型付け言語(Python, Rubyなど) で一般的に使われます。
ダックタイピングは「もしもそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルであろう」という言葉に由来しています。つまり、 オブジェクトがある型(クラス)に属するかどうかは、そのオブジェクトがその型に特有の振る舞いをするか(メソッドを持つか)どうかで判断するという考え方 です。
(「足し算ができるなら数値型だろう。。。」みたいな感じです)
いまいち掴みどころがないように感じるかもしれません。
とりあえず個人的な解釈でまとめると、そのオブジェクトの 型(クラス)に関しては気にせず 、代わりに 振る舞い(メソッド)に関心を払う 作法のことです。
この記事の中で度々「型(クラス)」と出てきますが、Rubyでは文字列型や数値型もそれぞれのクラスから生成されたオブジェクト(インスタンス)であることを念頭に置いているためです。
動的型付け言語 と 静的型付け言語
先ほどのダックタイピングの説明の中で「オブジェクトの型(クラス)を明示的に宣言せずに」とありましたが、この オブジェクトの型を明示的に宣言するかどうか が、「動的型付け言語」と「静的型付け言語」の大きな違いの一つとなります。
動的型付け言語 の特徴
- 型チェックがプログラムの 実行時 に行われます。よって、プログラムを実行している間に型の問題が検出されることがあります。
- 変数、関数の引数、戻り値の型が 明示的に宣言されない ことが一般的で、実行時に型が決定されます。
- メリット:型宣言が基本的にないため、開発速度が速く、コードがシンプルで読みやすいです。
- デメリット:型の問題が実行時まで検出されないため、実行時エラーが発生しやすいです。
静的型付け言語 の特徴
- 型チェックが コンパイル時 に行われます。なので、プログラムを実行する前に型の問題が検出されます。
- 変数、関数の引数、戻り値の型が 明示的に宣言される ことが一般的で、型情報を利用してコードの安全性を高めます。
- メリット:コンパイル時に型の問題が検出されるため、実行時エラーが発生しにくくなります。
- デメリット:コードの記述量が増えるため、開発速度が遅くなることがあります。
それぞれの具体例
ここで「動的型付け言語」と「静的型付け言語」の違いを、簡単なコードを例に見ていきましょう。
どちらもほとんど同じ動作をするコードですので、違いにのみ絞って確認できるようになっています。
以下のコードでは、addメソッドが引数aとbを受け取り、a + bという演算を実行しています。
動的型付け言語 (ダックタイピング) の例
def add(a, b)
a + b
end
result1 = add(1, 2) # 3
result2 = add("Hello, ", "World!") # "Hello, World!"
動的型付け言語であるRubyで書かれた上記のコードでは、引数の a と b が +演算子(メソッド)をサポートしているかどうか で、オブジェクトが期待される型であるかを判断しています。
つまり、a と b が +演算子をサポートしている(つまり、「アヒルのように歩く」)ならば、それらは加算可能なオブジェクトである(つまり、「アヒル」である)と判断しています。
(なんか小泉構文みたいな感じですねw その人を本人確認書類ではなく、小泉構文を使っているかどうかで小泉さんか判断する、みたいな感じですね ꉂꉂ(๑˃▽˂๑) )
静的型付け言語 (非ダックタイピング) の例
次に、ダックタイピングを用いずに、静的型付け言語のTypeScriptでほぼ同じコードを書いた場合の例です。
// シグネチャの定義(引数と戻り値の型を定義している)
function add(a: number, b: number): number;
function add(a: string, b: string): string;
// 実装部分(引数が数値の場合は数値を、文字列の場合は文字列を返す。その他の型の場合はエラー。)
function add(a: any, b: any): any {
if (typeof a === "number" && typeof b === "number") {
return a + b;
} else if (typeof a === "string" && typeof b === "string") {
return a + b;
} else {
throw new Error("Unsupported types");
}
}
const result1 = add(1, 2); // 3
const result2 = add("Hello, ", "World!"); // "Hello, World!"
この例では、add関数に2つのオーバーロードを定義しています。数値同士の加算と文字列同士の加算のシグネチャを定義しています。その後、実際の実装であるadd関数が引数の型に応じて適切な処理を行うようにしています。
上記のコードは、動的型付け言語でのダックタイピングとは真逆の例と考えることができます。オーバーロードを使用することで、引数の型が事前に明示的に定義され、コンパイル時に型チェックが行われるため、型安全性が確保されています。
まとめ
まず、事前に型(クラス)を宣言するのが「静的型付け言語」であり、事前に型(クラス)を宣言せず実行時に型を決めるのが「動的型付け言語」です。
そして、ダックタイピングとは、そのオブジェクトの型(クラス)に関しては気にせずに、代わりに 振る舞い(メソッド)に関心を払う作法 のことで、これは動的型付け言語の特性を用いた手法です。
最後に
お疲れ様でした!
以上でダックタイピングの説明を終わりにします。
実際にはTypeScriptにおいてもジェネリクスなどを用いることによってダックタイピングのようなことは実現できるようなのですが、今回はわかりやすさを優先してその辺りには踏み込みませんでした。
ぜひ余力のある方はTypeScriptも勉強すると良いと思います。認識が広がり、今後の学習の糧になると思います。
それではまた!(^_^)ノシ