Help us understand the problem. What is going on with this article?

動的型付けと静的型付け

動的型付けと静的型付け

世の中には多数のプログラム言語が存在しますが、必ず存在するのが変数、言語によって名称や扱い方が多少異なりますが、関数(サブルーチン、メソッド等いろいろ呼ばれ方はあります)というものがあります。
これら変数や関数でデータを扱うとき、大雑把に分けると2パターンあり、それが今回の話題として取り扱う「動的型付け」と「静的型付け」という考え方です。
単純な考え方としては、コードを書くときに、プログラマが予め、変数や関数にそれを扱う方法、つまり「型」を決めて使うことを静的型付け、コンパイラやインタプリタが、プログラムを実行時に、変数や関数を想定して「型」を当てはめて使うことを動的型付けといいます。

今回、この議題をあげたのは、開発時にデザイナーとのやりとりで、なぜ動的型付けと静的型付けがあるのか?動的型付けだけでいいんじゃない?、という議論を行ったのが要因で、その場で上手な説明が出来なかったので、整理してみました。

動的型付けとは

『動的に型を付ける』という読んでそのままの通り、プログラムを書くときに変数や関数に何が入ってくるかというのが特に決まっていない形を指します。要は、プログラマーは、変数や関数になんとなく、こんな感じのデータが入るであろうということを予測するだけで、どんな値が入るかはコーティングには影響なく、実行時に多少影響するぐらいで、型の管理はインタプリタやコンパイラが実行時にしてくれるという形態になります。
特にインタプリタで実行される言語は、ほぼこの形式を採用しています。型を宣言する必要がないため、小さいプログラムや型の変動が激しい環境であれば簡単に実装できる反面、動的型付けは事前にコンパイルしてネイティブで最適化されたコードを出力することは出来ません。1
これは、プログラムを実行しないと型がどんな形で、どんな大きさなのかを判別することができないため、実際にプログラムを動作させないとわからない、ということが起因します。

では、動的型付けの利点と、現実的に動的に型が決まるということがどういうことなのか、考えてみます。

 まず、動的型付けの利点は、データの型を気にしなくても良い点。もう一つはデータそのものの大きさを気にする必要が無い点です。一つ目のデータの型とは、「はい(true)、いいえ(false)」を扱う論理値、数値、小数点、正の数、負の数、文字、文字列、オブジェクト等など、様々な型が存在します。これらを変数や関数に入れたり渡したり拾ったりするとき、基本、気にする必要が無いです。もう一つはデータそのものの大きさ。数値ならば、0~10億や10兆やら、0~-10億や-10兆、0.000001 や、「あいうえおかきくけこさし...ん」という文字の長さまで、気にしなくても大丈夫です。最大限の数値または長さは当然、有限なのでそこだけを気にすれば...。

そのことを踏まえて、現実的な動的型の考え方。

Aさん「今から、カレー作る材料を持って行くから、スペースと包丁、まな板、ボール、鍋、皿を用意しておいて!」
Bさん「わかりました」

Bさん:あれ?どれだけの量持ってくるのかな?
Bさん:うーん、量は、Aさんが来るまでわからないので、一番家にある大きいのを用意しておこう。

ということで、カレー(分量)の分量がこの時点ではわかりません。特に、スペース、まな板、ボール、鍋、皿については来ない限りどれぐらいの大きさを用意すればわからないことになります。まぁ、Aさんも良く来るのであれば、Bさんの家に置いてある器具の大きさの最大値はわかっているでしょうし、スペースもどれぐらいが限界かわかっているとすると、最大値は固定化されます。このように、指示されるデータ容量がわからなくても柔軟に対応できるのが動的型の利点です。

プログラムに置き換えても、器具とかはわかっていても量がわからない(入力されるまでわからない)というのは結構あります。そもそも、カレーの作る材料というのも、この時点で「カレーの材料」であって、シーフード?野菜カレー?普通のカレー?なのかもわからないわけで(例えば、こういうのはWebAPIなどのJSONで返される仕組みを思い浮かべるとわかりやすいかも)、この時点でどういう素材が持ち込まれるのかわからないこともあって、その当たり実行時(持ってきてから確認して遂行する)しか出来ない感じになります。この、何が入るかわからない、設計が大変だと言うときは動的型付けが凄く利点になります。

 さて、現在のコンピュータは64bitが主流になってます。PHPやJavascript等を初めとした言語では、整数値、浮動小数点などは64bitで扱うことになります。メモリ使用量は8byte、最大「18,446,744,073,709,551,616」の数値の範囲で扱えます。(符号無しの場合)。動的型付けの言語では、数値を扱う場合、ほぼ8byteのメモリを使用します。
 では、インタプリタは型を決めるときに、最小に抑えることは出来ないのでしょうか?でも、この「最小に抑えたいな、こういう風にして欲しいな」というのは、その考え方が既に型を指定する(静的型付け)になるので、動的型付けではできませんね。また、動的型付けの型を付けるタイミングというのは、必ずプログラムを実行している時になります。なので、既にそのデータが小さいか、大きいかを確認してからサイズを決めているのでは効率が悪すぎます。なので、値やデータを扱う場合は、大きめに確保されて実行されます。

動的型付けでのプログラミング上の欠点

動的型付けのプログラミングの一番の欠点、それは、プログラムのコードが設計書にならないという点です。
どういうことかと言いますと...

Aさんは、javascriptで加算をする関数を書きました。
加算する関数として書いてます。

function add( a, b )
{
    return a + b;
}

この関数は、aとbを加算し、計算結果を返します。プログラマーの意図しているのは「整数?小数点?等の数値を入れると加算してくれる関数ですよ」という意図として作っている点です。そして、以下はすべて動作します。

1. add( 1, 5 ); // 6
2. add( 1.5, 3.2 ); // 4.7
3. add( 3, 3.5 );   // 6.5
4. add( "1", "5" );  // "15"
5. add( "hoge", "hoge" ); // "hogehoge"
6. add( [1, 3, 5], [6, 8, 9] ); // 1,3,56,8,9

1,2,3はともかくとして、4,5,6 は想定外な書き方をされたパターンです(結果はコメント分(//の後ろ)です。なお環境によって異なるかもしれません)。プログラマーが自分で作成して自分でこれを書いてしまった場合は、アホとしか言いようがないので避けられるといえば避けられます。でも他人が関わってくると、このようなパターンはあり得ます。というより、大人数になると本当にこれが良く発生します。特に、設計書がない!というパターン。

ちなみに、これをPHPで書くと

function add( $a, $b )
{
    return $a + $b;
}

変数に $ が付くだけですね。

1. add( 1, 5 ); // 6
2. add( 1.5, 3.2 ); // 4.7
3. add( 3, 3.5 ); // 6.5
4. add( "1", "5" ); // 6
5. add( "hoge", "hoge" ); // error
6. add( [1, 3, 5], [6, 8, 9] ); // 1,3,5

1,2,3は想定内です。4が「6」になるのは暗黙の型変換という言語機能による結果だと思います。PHPの場合、文字列結合はドット演算子「.」になりますので、ここでは数値型へ強制変換されたのかと。5については、当然エラーとなってしまいます。6については、結果が[1,3,5,6,8,9]とならず、[1,3,5] となるのは、PHPでは、配列の+演算子は右側の配列を左側の配列に追加し、また両方の配列に存在するキーについては左側の配列の要素が優先され、右側の配列にあった同じキーの要素は無視されてしまうからです。(上記では [ 0 => 1, 1=> 3, 2 => 5 ], [ 0 => 6, 1 => 8, 2 => 9 ] という感じで値が格納されていると思ってください)2

このコーティングミスを防ぐためには、関数内で数値型以外はエラーや例外処理を書くという必要があります。でも、それだと本末転倒な気がします...。なお、PHP7より、プリミティブに対しての静的型付けができるようになっているので、PHPでは

function add( int $a, int $b ) : int
{
    return $a + $b;
}

とすることで、

1. add( 1, 5 ); // 6
2. add( 1.5, 3.2 ); // 4
3. add( 3, 3.5 ); // 6
4. add( "1", "5" ); // 6
5. add( "hoge", "hoge" ); // error
6. add( [1, 3, 5], [6, 8, 9] ); // error

という、結果が得られます。浮動小数点型、文字列型は暗黙の型変換により int にそろえてくれたようです。エラーにしてほしいです。(しかも、PHPは関数のオーバーロードが無理なので(当たり前ですが)、int/float/string 用に作ろうとすると、関数名を変える必要があります。(PHP7.4以降で出来る可能性が出てきました...)

このように、動的型付けの場合、考え無しに実装すると実行時に思った通り動作しなくなり、そのバグの究明にかなり時間がかかることとなってしまいます。小さなプログラムであれば特定は簡単ですが、膨大なコード量になってくると窓からパソコンごと捨てたくなります。コードにコメントを書くことによってある程度、その命令の意図を伝えることは出来ますが、そのコメントを読まなかったりものすごく長いコードファイルの中にあったりすれば、辛いと思います。

なお、動的型のもう一つの欠点は、基本的に静的型よりも処理速度が遅くなる傾向にあります(機械語がわかっていると、理由はわかるはずです)

最後に、動的型付けに発生するコーティングミス(欠点)が、「undefined variable」です。

$name = "飛竜猫";
$yomi = "toryuneko";

// ()
echo "{$nama}({$yome})";

このコードは、飛竜猫(toryuneko) という結果を期待したのですが、空白しか得られません。理由は、定義している変数と違う変数名で記述した(つまり、間違えてる)からです。PHPの場合、基本はこのコードは無視されます(つまり空)。デバッグモードを有効にしていたすると、一応エラーとして表示されますが、リリースしたあとに結構気がつくことが多いです(ウェブの仕組みなので)。これは、echo で、変数の名前を間違っているだけに過ぎないのですが、結構、動的型付けの言語ではこれが多くなります。そして、実行時に気がつくと言うことです。このコードはこの量なので直ぐにわかりますが、大量のコードに埋もれている場合は、これが結構気づきません。

静的型付けでは、この変数の指示ミスはあっても、変数が存在しないものを指定するミスはありません。ミスをしていても、コンパイル時に存在しない(undefined)となります。

これが、動的型付けにおいて、一番面倒でやっかいなバグ(すごく気が付きにくい)になります。

ちなみに、javascript には、undefined というのがあります。これ、予約語とかではないようです。まぁ、C言語のNULLもただの定数ですが。
ちなみに、undefinedは、null とは違います。nullは、「空」を示しますが、undefinedは「存在しない」です。

静的型付けとは

ここが課題です。デザイナーとのやりとりで難しいと言っていたのは、変数や関数に型がなぜ必要なのか、オブジェクト型って何!といわれる点です(プログラマーでもオブジェクトという概念は、結構捉えどころが人それぞれで難しいです。あくまで、オブジェクトとはプログラム言語における概念であって、機械語としてのレベルでの話になると、スタックかヒープに置かれた大きなデータへの参照・ポインタであるというだけなので。)

静的型付けは、簡単に説明すると変数や関数に型を予め定義しておき、その型以外のデータを変数では使えません。この変数は整数値を扱う、負の数はだめ、小数点で扱いますよ、数値の羅列(配列)で扱うのですよ!ということを予め定義するものです。

動的型付けで例として述べた、カレーを作る作業は、動的型付けでは結局、何のカレーをどの量作るのか?というのがわからない状況で持ち込まるため、それを想定して用意をしなければなりませんでした。
今回の、静的型付けでの考え方では、カレーの材料から必要な分量(じゃがいも 何g n個数、にんじん 何g n個数等、ボールの大きさ、鍋の大きさ、個数)など、細かい情報が入るわけです。つまり、レシピが用意された形(設計書)で、こちらは用意(コーティング)を行うわけです。
 
静的型付けは、ネイティブコードに直接コンパイルできる言語はほぼ持っています。というのも、CPUが理解できる機械語というものに変換するには、型という情報がなければコンパイラは機械語に変換できないからです。機械語に型という情報は厳密にはありません。メモリ上で、その変数の情報にどのぐらいのメモリを確保し、そのデータはどのように扱うものなのかという判別が出来るぐらいです。コンパイラは、これを考慮して機械語を出力します。

int a; // (4byte)
double b; // (8byte)
char c[6]; // (6byte)

型があるコードをコンパイルすると、変数順に、4, 8, 6 byteの容量をメモリ上のスタック領域またはヒープ領域に確保するコードが出力されます。
コンパイラが出力した機械語には、そのデータはどのメモリ範囲にデータがあるか、という情報のみが書かれており、4,8,6byteのデータがどのような型(int,double,char[])であるのか、という情報はなくなっています。そこにあるのは、あくまで数値的なデータだけです。なお、データそのものはただの数値の羅列として扱われますが、各機械語の命令を通すことで、それが数値・浮動小数点・バイナリ配列・文字列だったのかというのがわかるようになっています。

さて、これを動的型付けに置き換えて

var a;
var b;
var c;

としたところで、これは何が入るのでしょうか。そして、どのようにメモリを配置すればいいのでしょうか。この情報だけでは、コンパイルしてもメモリでの操作を行うコードはおろか、メモリの配置容量や先をも決めることは出来ません。なので、動的型付け言語では、直にネイティブコンパイルができないのです。そのため、インタプリタで逐次実行しながら、動作の過程でわかる型を確認し、JITコンパイルを利用してコンパイル(機械語に出力)を行う形が主流になっているのです。

理由はわかったけど

静的型付けが必要な理由は、事前にネイティブにコンパイルするには必須であるということです。特にiOSのネイティブアプリを開発するには、動的言語(インタプリタ専用の言語)が利用できないということで、覚えてください...ということになったのですが、この静的型付けも種類が多く、また言語によって特徴もあってなかなかややこしいです。
特に、オブジェクト型(インスタンスを生成するのに)に対して、なぜ型が必要なのかと聞かれたとき、どのように答えればいいか困ったものです。概念的なことはプログラマーですので頭の中でこういう理由なのだが、というのはわかるのですが、プログラマーではない一般の人?というのにそれを理解してもらうのは結構難しいです。機械語(物理メモリに関すること)に影響するので(そもそも機械語に型はない。厳密には全くないわけではなく、値を扱うときにAMD64(x86_64)の場合、機械語レベルで32bit値は eax レジスタを、64bitは rax を使うなど、レジスタに応じた命令が用意されているため。
このあたりは、いかに機械語・CPUやコンピュータの仕組みを理解できているかによるため、なかなか難しい問題ではあります。

まとめ

猫は動的型付け言語もPerl/PHP/Ruby/Javascript/VBScriptと触ってきていますが、やはり静的型付け言語の方が好きです。変数名をしっかりと名付けることで、その変数は文字列なのか数値なのか、配列なのか、オブジェクトなのかというのがわかる...という仕様にしてしまえ場いいのですが、結局他人と共同でやったり、時がたてば、そのコードの意図など忘れてしまいます。そういう点では、静的型付け言語で書いておけば、その意図が型として定義づけられているので、時がたっても思い出せるのではないか...思います。
とはいえ、最近流行のJSONを静的型付け言語で処理するのは結構大変です。特に、自分で作るシステムならば問題ないですが、他人が作ったシステムを利用とした場合、その構造が多岐にわたってくるとその分の構造を予め作っておく必要があります。それはそれで結構大変です。動的型付けの場合は、一度受け取った後で分解させる処理を書いていけば済むわけです。なので、どちらが優れている、欠陥だというよりは、適材適所に合わせて使うのが一番いいのでしょう。

ちなみに、猫の好きなC#言語は、静的型付け言語なのですが、dynamicという、動的型付けできる型が存在しています。C++言語にもRTTIとよばれるものもありましたので、静的言語に動的言語を扱えないというわけではありません。
ただ、動的な型は、実行時にサイズがわからないため、メモリ容量を多めにとったり、実行時にどの程度のサイズになるかを計測する命令を通り、当然ながら機械語にあるようなレベルの型の扱い(2byteをメモリから読み込む、4byteをメモリから読み込む)という単純命令ができないため、処理はかなり遅くなります。
また、動的型はインテリセンス(IDEの入力補完機能)が型がわからないので、その変数に何が入れられるかとういのがわからないことと、動的型特有の実行時に判別するので実行時エラーにより思った通りの動作ができない(不具合の究明に死ねる)という弱点がありますが、適材適所で使うと利便性が高まります。


  1. 当初、動的型付けではネイティブにコンパイルできないと書いていましたが、RTTI(RunTimeTypeIdentification:実行時型情報)などの仕組みを使うことで、動的型的な情報をネイティブにはコンパイルは可能です。この記事内では、ネイティブにおけるプリミティブなデータ関係でコンパイルできないと意味合いで書いていました。 

  2. 当初、6番目はエラーと書いていましたが、var_dumpで結果を確認せず、echo でしてしまっていました。失礼しました。また、ご指摘ありがとうございます。 

toryuneko
三重県四日市市在住の気ままでプログラマーな猫。ネットワーク関係やサーバー構築もこなします。本業はウェブのバックエンドプログラマーですが、趣味でWindowsやAndroidアプリ作ったり、ツール作ったり。 好きな言語はC#言語、嫌いな言語はJava言語。 Windows/RHEL派。Macも仕事柄仕方なく使うけど、敵だと思ってる。
https://nekochaya.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away