静的型付けでの型推論と動的型付けでの型チェック

最近の言語では 「静的型付けは型推論」、「動的型付けは型チェック」 と同じようなところに向かっている傾向がみられます。 今回は最近のプログラミング言語におけるこれらの型システムについて書いてみたいと思います。

静的型付けと動的型付け

まず、静的型付けと動的型付けについて簡単に説明します。

静的型付けでは 変数を宣言するときに型も指定します。 整数用の変数には整数のみ、文字列用の変数には文字列のみと、 一つの変数には一つの型しか入れることができません。
int    foo;
string bar;
bar = 3;    // エラー
一方、 動的型付け では 変数に任意の型を入れられるようになっています。
var foo;
foo = 1;
foo = "Hello";
var bar;        // 未初期化の場合は null や undefined などの値

動的型付けのメリットは、その手軽さです。
自由度が高く、後述するダックタイピングもできます。これにより、配列などのコンテナーに任意の型を格納することも可能になります。
Ruby 、 JavaScript をはじめとしたスクリプト言語の多くは動的型付けが採用されています。

静的型付けのメリットはというと速度です。
動的な型では自由度の代償として、高速なコンパイルコードの生成が難しくなります。 静的な型でなければ、コンパイルできないということではありませんが、 C++ のようなコンパイル型の言語では速度を求められることが多いので、たいてい静的型付けです。

Java, C# のような仮想マシン(VM)の言語も静的型付けが多いです。 VM では実行時(JIT)コンパイルを使って高速化されることが多く、 それには静的型付けがよいためです。
ただ、最近では動的型付けでも JIT コンパイルも高速化はされてきています。 なお、 動的型付けはスクリプト(インタプリター)型言語、静的型付けはコンパイル型言語というのは、 そういうったものが多いというだけで、例外もあります。

静的型付けにおける型推論

動的型付けの方が楽ですが、速度を求められる場合は静的型付けにする必要があります。
でも、やっぱり面倒だから、コンパイラーに頑張らせて、省略できるところは省略しようというのが型推論です。
例えば、次のような C++ のコードでは、初期化しているので、型が何かコンパイラーは分かります。 型推論の機能が言語にあれば、こういう時に省略できます。
int    foo = 3;
string bar = "Hello";
Scala、 C# など最近の静的型付け言語では型推論に対応しているのが普通になってきました。


なお、型推論は動的型付けに似ていますが、別物です。 JavaScript と型推論用の C# の変数宣言は同じ var を使いますが、違いがあります。

型推論 :
var foo = 3;   //  整数型で定義したのと同じ
foo = "Hello";  // foo は整数型なのでエラー
var bar;        // 初期化しないと特定できないのでエラー

動的型付けにおける型チェック

動的型付けにはコードの安全性という面で欠点があります。
例えば、次のような Ruby のメソッド(関数)定義があったとします。
def foo(num)
  # ...
  bar = num + 1  # 数値ではないとエラーになる
  # ...
end
この場合、動的型付けに起因する次の問題があります。 これはライブラリーなどの場合に特に顕著になります。
  • メソッドの内部でエラーが発生する
    • 中身までみたくない、エラー原因を特定しづらい
  • メソッドの引数の型をコメントで書く必要がある
    • コメントに頼らないコードがよいコード
この対応として、メソッドの引数、戻り値(入出力)でのみ型指定して、型チェックができる言語があります。
PHP(Hack) や Dart, TypeScript といった JavaScript の改良言語などです。
 int foo(int bar) // 引数、戻り値で型指定
 {
   // ...
 }
 
 foo("Hello"); // foo() を使用するときに型チェックされる
また、引数の型指定ができるのであれば、関数のオーバーロードもできるようになります。 この機能があれば、より使いやすい言語になると思います。


これらの理由により、もともと型指定のある静的言語ではローカル変数だけ型推論で省略し、 型指定のない動的言語では関数宣言のところだけ型指定するという傾向ができています。

ダックタイピングとインターフェース

関数で引数の型指定をしないのは悪いことのように書きましたが、 メリットもあります。 それはダックタイピングできることです。

ダックタイピングとは "アヒルのように振舞えば、それはアヒル" という言葉に由来しています。
例えば、 Ruby で次のような構文解析用のメソッドがあったとします。
def parse(fp)
  # ...
  while (str = fp.gets())   # 一行読み取る処理
    # ...
  end
  # ...
end
この場合、渡す引数 fp には一行読み取る gets() というメソッドを持っているかだけ が重要となります。 gets() さえ実装されていれば、文字列でもファイルでも使えますし、 圧縮ファイルやネットワーク通信を仮想化したものでも構いません。


ただし、これはいいことばかりでなく、先ほどの型チェックと同じ問題があります。 使用可能なものをコメントで説明する必要があったり、変数のクラスが必要なメソッドを実装しているか保障されていなかったりします。
この保障を与えるのがインターフェースです。 これは Java, C# といった言語にある機能で、 C++ でも純粋仮想関数を使って作ることができます。 また、動的型付けでも Dart のように型チェック機能がある言語ではインタフェースを持つものもあります。


しかし、言語にインタフェースの機能があったとしても、インターフェースが常に使えるわけではありません。
例えば、先ほどのファイルオブジェクトの場合などは標準クラスのものが多く、 後でインターフェースを継承させるといったことは難しいでしょう。

オプショナルな型チェックと自動ジェネリック

ダックタイピングしたい場合もあれば、インターフェースなどで型指定したい場合もあります。
動的型付けでは型チェック機能があった方がいいと書きましたが、 ベストはオプショナルな型チェックです。 これは型指定していれば型チェックし、無ければダックタイピングとなります。
ただ、動的言語の場合には、もともと型チェックしていないところに追加しているので、 オプショナルにするのはそれほど難しくなく、型チェックはオプショナルになっているのが普通です。
 int foo(int bar) // 型チェックあり
 {
   // ...
 }
 
 foo(bar)         // 型チェックなし
 {
   // ...
 }
静的型付けでも両方できるのが理想です。
ただし、静的型付けは本来型指定するものなので、 C++ のテンプレート(ジェネリック)のようなダックタイピングを実現する機能が必要となります。 こういったジェネリックの機能を持つ静的型付け言語は結構あるのですが、 C++ のテンプレートのように書くのが面倒なものが多いです。
しかし、最近では F#(OCaml) のように型指定を省略すれば、自動的にジェネリックになる(自動ジェネリック化)機能を持った言語も増えてきました。
 // 型指定を省略するだけでジェネリック
 let max a b =
   if a > b then a else b
 
 let biggestInt = max 2 3
 let testString = max "cab" "cat"

JavaScript 変換言語における型システム

型システムに関して、以前の記事の補足的な話もしておきます。 静的型付けのメリットは速度と書きましたが、これが当てはまらない分野の言語があります。 それは altJS などと呼ばれる JavaScript に変換して使う言語です。 これらの言語ではJavaScript に変換されるので、動的型付けに戻ってしまうためです。

以前の記事では、型指定の機能は 一緒くた にしていましたが、実は中身は微妙に違います。

Dart や TypeScript は関数のオプショナルな型指定であり、 これは純粋に利点です。
しかし、 Haxe や JSX は静的型付けになっています。 これは JavaScript コードの生成だけを見れば、やりすぎと言ってよいでしょう。 生成する JavaScript コードの最適化に多少は役に立っているのかもしれませんが、 大きな速度のメリットもなく、手軽さが失われています。
この 2 つの言語は JavaScript 以外の言語への生成も考慮されており、 そのための仕方ない処置とも言えます。 また、型推論もあるので、そんなに気になるほどの欠点でもないです。

ちなみに Dart に関しては、最終的には JavaScript に置き換えて、 Dart のまま動作させるので、静的型付けによる速度のメリットはあります。 ただ、 Dart は他のスクリプト言語同様に手軽さのためか動的形付けが採用されています。
また、実際には Dart は関数宣言だけでなく、ローカル変数でも型指定できますが、 スタイルガイドで指定しない方を推薦されています。

まとめ

いろいろ書いてきましたが、静的型付け、動的型付け、どちらがいいという問題ではありません。
静的型付けは安心感を与えるので、静的型付け派の人もいますが、 個人的には適材適所で、スクリプト言語であれば動的型付け、コンパイル言語では静的型付けでよいと思っています。
ただ、静的型付け、動的型付けのそれぞれともメリット、デメリットがあります。 このデメリットを軽減させる機能が型推論、型チェックです。

静的型付けでは型推論に加えて、自動ジェネリックの機能もあれば、型指定に関してスクリプトとほぼ同じ書きごごちが得られます。
なお、"型推論や自動ジェネリックで手軽さが得られるのであれば、スクリプト言語でも静的型付けにして速度向上できるのではないか" と思った人もいるかもしれません。 しかし、これらの機能は、ともにコード解析に結構負担をかける機能です。 コード解析に時間をかけられないスクリプト言語では難しい面があります

動的型付けでは型チェック、特にオプショナルな型チェックがあれば、 手軽さとダックタイピングのよさを残したまま、コードの安全性が向上します。 さらに関数のオーバーロードもできれば、より良いでしょう。

これら型推論、型チェックなどの機能を持つ言語が増えてきてます。今後もそれが進んで欲しいと思います。


関連記事
スポンサーサイト
Prev.    Category    Next 

Facebook コメント


コメント

コメントの投稿

Font & Icon
非公開コメント

このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
40位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
4位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

03月 | 2017年04月 | 05月
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。