932
682

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

動的型付けと静的型付け

Last updated at Posted at 2018-10-23

はじめに

 世の中には、多数のプログラミング言語が存在しますが、ほとんどのプログラミング言語において必須の言語機能として定義されているのが「変数」と、言語によって多少名称や扱い方が異なりますが「関数(言語によってはサブルーチンや、クラスにおけるメソッド等)」があります。これら変数や関数では、プログラマーがデータを扱うとき、大まかに大きく2つの型付け方式に分類されます。それが、今回の話題として扱う「動的型付け」と「静的型付け」です。

 簡単に言えば、「プログラマー」がコードを書く際に、あらかじめ変数や関数の引数・返り値に対して、どのようなデータ型を扱うかを明示的に指定する方法を「静的型付け」と言います。一方、コード上で型を明示せず、実行時にインタープリターなどが変数や関数の引数・返り値の型を判断・決定して扱う方法を「動的型付け」と言います。

補足:なお、静的型付け言語であっても、C++のRTTI(Run-Time Type Information)のように、実行時に型情報を扱う仕組みが存在する場合があります。これは、コンパイラーが型情報を実行時に利用できるようにコードを生成することで、動的な型判定やキャストを可能にする技術です。ただし、これは静的型付けの基本的な性質に対する補助的な機能であり、動的型付け言語とは根本的に異なります。

 今回この議題を取り上げたのは、開発時にデザイナーとのやり取りで、なぜ動的型付けと静的型付けというものがあるのか?動的型付けだけあればいいのではないか?という議論を行ったのが要因で、その場で適切な説明ができなかったため、改めて整理をしてみることにしました。

そもそも型とは何か

 プログラムにおける「データ」とは、論理値(真偽値)、数値、文字列、配列、オブジェクトなど、さまざまな形で扱われます。しかし、これらはあくまで抽象的な概念であり、コンピューター内部、すなわち物理メモリ上では、すべて単なる数値の羅列として表現されています。さらに、コンピューターが扱えるデータには有限の範囲があり、無限の表現はできません。

 このような曖昧な数値の羅列に意味を与えるために存在するのが「型(データ型)」です。型は、あるデータがどのように解釈され、どのように処理されるべきかを、コンパイラーやインタープリターに伝えるためのルールや指示の役割を果たします。たとえば、同じ「01000001」というビット列でも、「整数」として扱うのか、「文字('A')」として扱うのかによって、意味も処理もまったく異なります。

 つまり、抽象的なデータに対して「型」を定義することで、プログラマーはそのデータの意味や使い方を明確にし、コンピューターが正しく処理できるようにするのです。そしてその型の扱い方には、プログラマーがあらかじめ型を明示する静的型付けと、実行時に型を解釈する動的型付けという2つのアプローチがあります。

よくある勘違い

 静的型付けと動的型付けを説明する際によく見られる誤解に、「コンパイラーを使う言語は静的型付けである」「インタープリターを使う言語は動的型付けである」といった説明があります。しかし、これは正確ではありません。

 たとえば、静的型付けの言語であっても、**RTTI(Run-Time Type Information:実行時型情報)**と呼ばれる仕組みによって、実行時に型情報を扱うことが可能です。C++、C#、Delphi(Object Pascal)などのコンパイル型言語では、動的な値を扱うための型(例:dynamic型やvariant型など)が用意されており、実行時に型を判定・処理する機能が存在します。

 一方、インタープリター型の言語でも、近年では型情報を事前に定義できる機能を持つものが増えています。たとえばPHPでは、変数には型を指定できないものの、関数の引数や返り値、クラスのプロパティなどには型を指定することが可能です。また、JavaScriptのような動的型付け言語に対して、型安全性を補うためにTypeScriptのような静的型付けを導入した派生言語も登場しています。

 このように、コンパイル方式(AOTや中間言語)かインタープリター方式かに関係なく、静的型付け・動的型付けの両方を扱える言語設計は存在するということです。型付けの方式は、実行形態ではなく、言語仕様や設計思想によって決まるものなのです。

コンパイルとインタプリタ

まず、前提知識として「型」を押さえる前にプログラムの実行形態について簡単に説明します。

 コンパイラとは、ソースコードを読み取り、別の言語形態(機械語に変換、別の高級言語に変換など)に変換(翻訳)を主とするプログラムのことを「コンパイル(翻訳の意)」といいます。
 インタプリタとは、ソースコードを読み取り、翻訳・実行を同時に行うことを主とするプログラムのことを「インタプリタ(通訳者の意)」といいます。
 ちなみに、一般にコンパイル型の言語として知られているものでも、インタプリタとして動作させることが可能な場合があります(例えばC言語やC#など)。そのため、「コンパイル型言語」や「インタプリタ型言語」といった分類は、厳密には成立しないケースもあります。

 本記事では、こうした分類にはとらわれず、「コンパイル」と「インタプリタ」という実行形態の違いに着目して話を進めていきます。

コンパイルとは

 「コンパイル(Compile / Compiler)」とは、主にソースコードを別の言語形態(機械語や他の高級言語など)に翻訳・変換する処理を指します。
コンパイルは基本的に翻訳までしか行わず、実行は別工程になります。たとえば、リンカを通してライブラリやフレームワークなどを結合し、実行可能なファイルを生成する必要があります。これら一連の処理(コンパイル・リンクなど)をまとめて「ビルド」と呼びます。
 また、変換された中間言語をさらにコンパイルしたり、インタプリタで実行したりするケースもあります。
現実的なたとえとしては、「海外の書物を、事前に現地語に翻訳しておく」ようなイメージです。

インタプリタとは

 「インタプリタ(Interpret / Interpreter)」とは、ソースコードを読み取りながら、その場で翻訳と実行を同時に行う処理を指します。
 たとえ話で言えば、「海外の人が話している内容を、通訳者がその場で現地語に翻訳して伝える」ようなイメージです。

🧭 まとめ

このように、コンパイルとインタプリタは、どちらも「翻訳する」という点では共通していますが、翻訳後の扱い方が異なります。
インタプリタの場合、翻訳してもソースコードは手元に残っており、実行が完了するまでインタプリタがそれを参照し続けます。そのため、実行時に型の意味を考える必要があっても、元のコードから辿ることが可能です。

一方、コンパイルされた場合は、ソースコードが翻訳されると、翻訳された成果物(機械語など)だけが残り、元のコードは基本的に失われます。そのため、実行時に型の意味が不明であれば、元の情報にアクセスできず、実行できない可能性があるということになります。

動的型付けとは

「動的に型を付ける」とは、プログラムの実行時に変数や関数の型が決定される形態を指します。
プログラマーは、変数や関数にどんな型のデータが入るかを事前に明示する必要がなく、インタプリタやコンパイラが実行時に型を判断して処理を行います。

※特にインタプリタ型の言語(例:JavaScript、Python、PHPなど)はこの形式を採用しています。

✅ 利点

  • 型を意識せずにコーディングできる
    論理値、数値、文字列、オブジェクトなど、どんな型でも変数に代入可能。
  • データのサイズを気にしなくてよい
    例えば、文字列の長さや数値の範囲を事前に指定する必要がない。

❌ 欠点

  • 型エラーが実行時にしか発覚しない
    変数名のミスで undefinednull が出力されることも。
  • 型変換のコストがかかる
    文字列→数値、数値→文字列などの変換処理が増える。
  • 型チェックのためのコードが増える
    if 文などで型を確認する処理が多くなりがち。
  • ネイティブ最適化が難しい
    実行前に型が確定しないため、事前コンパイルによる最適化が困難。

動的型付けのイメージ例

🍛 カレーの材料のたとえ話

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

Bさんは、どれだけの量が来るかわからないため、最大サイズの器具を用意します。
これは、実行時に何が来るかわからないが、柔軟に対応するという動的型付けの考え方に似ています。

実際のプログラムでも、WebAPIのJSONレスポンスなど、実行時まで構造が不明なデータを扱う場面があります。


実行時の型決定とメモリの扱い

現在のコンピュータは64bitが主流であり、PHPやJavaScriptなどの動的型付け言語では、整数や浮動小数点は通常8byte(64bit)で扱われます。
つまり、型が未定でも最大サイズを確保しておくという設計になります。

❓最小サイズで確保できないのか?

「最小限に抑えたい」という考え方は、すでに型を指定する=静的型付けの発想です。
動的型付けでは、実行時に型が決まるため、事前にサイズを最適化することはできません


🧭 まとめ

動的型付けは、柔軟性と簡便さを提供する一方で、型の曖昧さによるバグやパフォーマンスの課題も抱えています。
その特性を理解した上で、用途に応じて使い分けることが重要です。

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

❌ 設計書にならないコード

動的型付けの最大の欠点は、コードそのものが設計書にならないことです。
つまり、関数や変数の型が明示されていないため、何を渡せば正しく動作するのかがコードから読み取れないのです。

JavaScriptの例

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

この関数は「加算」を目的としていますが、以下のように型によって挙動が大きく変わります。

add(1, 5);           // 6
add(1.5, 3.2);       // 4.7
add(3, 3.5);         // 6.5
add("1", "5");       // "15"
add("hoge", "hoge"); // "hogehoge"
add([1,3,5], [6,8,9]); // [1,3,5,6,8,9](環境によって異なる)

1〜3は想定通りですが、4〜6は意図しない動作になりがちです。
特に他人が関わるプロジェクトでは、こうした型の不一致が頻発します。

phpの例

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

PHPでは、以下のような結果になります:

add(1, 5);           // 6
add(1.5, 3.2);       // 4.7
add(3, 3.5);         // 6.5
add("1", "5");       // 6(暗黙の型変換)
add("hoge", "hoge"); // エラー
add([1,3,5], [6,8,9]); // [1,3,5](配列の+演算子の仕様)

※配列の加算は、左側のキーが優先され、右側の同じキーは無視されます【^1】

🛠 静的型指定による改善(PHP7以降)
PHP7からは、プリミティブ型に対して静的型を指定できるタイプヒント(Type Hint)機能が導入されました。

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

このようにすると、以下のような結果になります:

add(1, 5);           // 6
add(1.5, 3.2);       // 4(float → intに変換)
add(3, 3.5);         // 6
add("1", "5");       // 6(string → intに変換)
add("hoge", "hoge"); // エラー
add([1,3,5], [6,8,9]); // エラー

※PHPは関数のオーバーロードができないため、型ごとに関数名を変える必要があります。

⚠️ 実行時エラーの罠:undefined variable

動的型付けでは、変数名のミスが実行時まで発覚しないことがあります。

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

echo "{$nama}({$yome})"; // 空白になる

このコードは nama や yome が未定義のため、空白が出力されるか、エラーになる可能性があります。
PHPではデバッグモードでなければ無視されるため、リリース後に気づくことも多いです。

🐢 処理速度の問題

動的型付けは、型を実行時に判断するため、静的型付けよりも処理速度が遅くなる傾向があります。
これは、型チェックや型変換のコストが発生するためです。

🎯 まとめ

  • 動的型付けは柔軟で便利だが、設計の曖昧さや実行時エラーのリスクが高い。
  • 小規模なコードでは問題になりにくいが、大規模開発ではバグの温床になりやすい。
  • 静的型指定やコメント、テストコードなどで補う工夫が必要。

静的型付けとは

ここが課題です。
デザイナーとのやりとりで難しいと感じたのは、「変数や関数に型がなぜ必要なのか?」「オブジェクト型って何?」という問いにうまく答えられなかったことです。
実際、プログラマーの間でも「オブジェクト」という概念は人によって捉え方が異なり、なかなか一言で説明しづらいものです。
猫(筆者)の考えでは、オブジェクトとはあくまでプログラミング言語における抽象的な概念であり、機械語レベルで見れば、**スタックやヒープに配置された大きなデータへの参照(ポインタ)**にすぎないと思っています。

さて、静的型付けとは、変数や関数に対して、あらかじめ扱うデータの型を明示的に定義する方式です。
定義された型以外のデータは使用できず、型の不一致はコンパイル時にエラーになります

int a;      // 整数型(4byte)
double b;   // 浮動小数点型(8byte)
char c[6];  // 文字列型(6byte)

このように、型を定義することで、コンパイラは必要なメモリ容量や配置方法を決定し、機械語に変換することができます。

🧠 なぜ型が必要なのか?
CPUが理解するのは「数値の羅列」だけです。
その数値が「整数」なのか「文字列」なのか「配列」なのかを判断するためには、型の情報が必要です。
静的型付けでは、コンパイル時に型が確定しているため、以下のような利点があります:

  • メモリの配置が最適化される
  • 実行前に型エラーを検出できる
  • ネイティブコードへのコンパイルが可能

🍛 カレーのたとえ:静的型付け編
動的型付けでは「何のカレーをどれだけ作るか」が不明なまま準備する必要がありました。
一方、静的型付けでは、レシピ(設計書)が事前に用意されている状態です。
じゃがいも:200g × 2個
にんじん:150g × 1個
鍋:直径30cm × 1個
ボウル:2L × 2個

このように、必要な材料・分量・器具のサイズが明確なので、準備(コーディング)もスムーズに進められます。

🧩 オブジェクト型とは?
「オブジェクト型って何?」という質問はよくあります。
オブジェクトとは、複数のデータ(プロパティ)と処理(メソッド)をひとまとまりにした構造です。

let user = {
  name: "飛竜猫",
  age: 22,
  greet: function() {
    console.log("にゃーにゃーにゃー");
  }
};

技術的には、オブジェクトは**ヒープ領域に配置されたデータの参照(ポインタ)**であり、
静的型付け言語では、オブジェクトの構造や型も事前に定義されます。

⚙️ 静的型付けとコンパイルの関係

静的型付けの言語は、ネイティブコードに直接コンパイルできることが多いです。
なぜなら、コンパイラが機械語に変換するには、型情報が必要不可欠だからです。
一方、動的型付けでは、以下のようなコードでは型が不明です:

var a;
var b;
var c;

この状態では、メモリの配置も処理方法も決定できないため、
インタプリタで逐次実行しながら型を判断し、JITコンパイルで対応する形になります。

🎯 まとめ

  • 静的型付けは、設計書のように型情報を明示することで、コードの安全性と効率を高める。
  • コンパイル時に型が確定しているため、実行前にエラーを防ぎ、最適なメモリ配置が可能。
  • オブジェクト型も含め、複雑な構造を事前に定義できるのが静的型付けの強み。
    デザイナーさんが「静的型付けいらなくね?」と言うのもわかりますが、
    設計書を書かないプログラマーこそ、静的型付けが助けになることも多いのです。

理由はわかったけど

静的型付けとネイティブコンパイルの関係

静的型付けされていれば、ネイティブコードへの変換は効率よく行うことができます
型が事前に確定していることで、コンパイラはメモリ配置や命令選択を最適化できるからです。

一方、動的型付けでも JIT(Just-In-Time)コンパイルを使えば、実行時にネイティブコードへ変換することは可能です。
ただしこの場合、JITが実行時に型を推論・判断しなければならず、処理コストが高くなります


🧠 型は「なくても変換できる」が「なければ効率が悪い」

結局のところ、ネイティブにコンパイルするには、型の情報は概念的に不可欠です。
効率のよいコードを生成したいのであれば、静的型付けはほぼ必須といえるでしょう。


🧩 説明の難しさ

この話は、最終的には機械語やコンピュータの仕組みに触れることになります。
プログラマーでもこのあたりまで踏み込まない人は多く、デザイナーや非技術者に説明するのはかなり難しいのが現実です。

特に「オブジェクト型って何?」「なぜ型が必要なの?」と聞かれたときは、答えに困ることもあります。
猫(筆者)の考えでは、オブジェクトとはプログラミング言語上の抽象的な構造であり、
機械語レベルでは、**スタックやヒープに置かれた大きなデータへの参照(ポインタ)**にすぎません。


⚙️ 機械語における型の扱い

そもそも、機械語には「型」という概念は存在しません

※ここでいう「機械語」とは「アセンブリ言語」ではなく、「バイナリの数値で表されたオペコード(Opcode)、オペランド(Operand)、時にはデータ」のことを指します。
Opcode = Operation Code (操作コード)という意味
Operand = Operation + -and(受け手というラテン語) と受け手いう意味

ただし、値のサイズに応じて使用されるレジスタが異なるため、間接的に型のような扱いはあります。

例えば、AMD64(x86_64)では:

  • 32bitの値 → eax レジスタ
  • 64bitの値 → rax レジスタ

このように、CPUの命令セットがデータサイズに依存しているため、コンパイラは型情報をもとに適切な命令を生成します。

※補足:
機械語には「int型」「float型」といったラベルは存在しませんが、
命令のサイズやレジスタの種類によって、結果的に“型的な振る舞い”が現れるため、
コンパイラは高級言語で定義された型情報をもとに、適切なサイズの命令を選択して機械語を生成します。


🎯 まとめ

  • 静的型付けは、ネイティブコンパイルに「絶対必要」ではないが、効率的なコード生成にはほぼ不可欠
  • 型の概念がなければ、メモリ配置も命令選択もできないため、JITでも内部で型を推論している
  • この話は、CPUやメモリの仕組みに関わるため、技術者以外に説明するのは難しいが、理解しておくと設計の質が変わる
  • 型が存在することで、コンパイラは「この値は何バイトで、どの命令を使って、どのレジスタで処理すべきか」を判断できます。
  • つまり、型は“人間のためのラベル”であると同時に、“機械語を正しく生成するための設計図”でもあるのです。

最後に

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

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

932
682
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
932
682

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?