型
Last Updated: 2026-09-28 02:00
結論
melangでは静的型付けが理想的である。型の不整合は実行するまでもなくエラーにしたい一方、型注釈を省略しても推論できる範囲はコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。に任せる。
理想としては、次のように書けることを目指す。
// 型注釈あり
val age: Int = 20
// コンパイラが推論できるなら省略できる
val age = 20
// 型が合わない代入は、実行するまでもなくエラーになる
val age: Int = "20" // コンパイルエラー
ただし、「静的型付けにする」で型に関する設計がすべて決まるわけではない。型推論の範囲、基本型の構成、Generics や Interface / Trait の設計、Null の扱いなど、個別の論点はそれぞれ別のnoteで扱う。
他言語比較
静的型付けと動的型付けの違いは、型の整合性をいつ検査するかにある。型システムの設計では、プログラミング言語は大きく2つに分かれる。プログラムを実行する前に検査する「静的型付け」と、主に実行時に検査する「動的型付け」である。主要な言語がどちらを採用しているかを比較する。
| 言語 | 型付け | 説明 |
|---|---|---|
| Rust | 静的型付け | 型注釈も型推論も両方使える。 型注釈は省略でき、コンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が文脈から型を推論する。Generics や trait と組み合わせた表現力の高い型システムを持つ。 |
| MoonBit | 静的型付け | WebAssembly向けに設計された新しい言語。 Rust/OCamlに近い構文を持つ、WebAssemblyをファーストクラスのターゲットとする新しい言語。型推論・代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。・パターンマッチを備えつつ、コンパイル速度とバイナリサイズの小ささを重視して設計されている。 |
| Swift | 静的型付け | 型推論が強く効く。 型推論が強力で型注釈を省略できる場面が多い。Kotlinと同様、Optional型によってNullの有無を型で表現する。 |
| Kotlin | 静的型付け | 型推論が強く、Null安全が型に組み込まれている。 型推論が強力で、型注釈を省略した書き方が主流。`String`と`String?`のようにNull許容/非許容を型システムで区別するのが特徴。 |
| Java | 静的型付け | 型注釈を書く場面が比較的多い。 ローカル変数の型推論(`var`)はJava 10以降で使えるが、基本的には型を明示して書くスタイル。 |
| Scala | 静的型付け | 強力な型推論を持ち、OOPとFPを両立する。 `val`(不変)/`var`(可変)で束縛の可変性をキーワードで区別する。case classとパターンマッチにより代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。に近い表現ができ、JVM上で実務的なFPスタイルを実現している。 |
| C# | 静的型付け | 型推論(var)はあるが、明示するスタイルも根強い。 `var`によるローカル変数の型推論が使えるが、型を明示するスタイルも広く使われる。Nullable参照型(`string?`)でNull許容を型として区別できる。 |
| C++ | 静的型付け | 伝統的に型を明示するスタイルが強い。 `auto`によるローカル変数の型推論はC++11以降で使えるが、関数シグネチャなどでは型を明示するのが伝統的なスタイル。テンプレートによる静的なジェネリックプログラミングも特徴。 |
| C | 静的型付け | 型推論の構文が伝統的になく、常に型を明示する。 型推論の構文が伝統的になく、変数は常に型を明示して宣言する。C23でようやく`auto`による限定的な型推論が追加されたが、対応は発展途上で、実務上はRustやGoよりずっと遅れて型推論を手に入れた言語という位置づけになる。ポインタ・配列・関数ポインタが絡む宣言は複雑になりやすく、後発言語が宣言構文自体を見直す動機の一つになった。 |
| Go | 静的型付け | 型推論はあるが、明示する場面も多い。 `:=` を使った短い変数宣言では型推論が効く一方、関数シグネチャなどでは型を明示するのが基本。 |
| Zig | 静的型付け | 型推論があり、型そのものをcomptime値として扱える。 `const`/`var`宣言では型注釈を省略でき、コンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が初期化式から型を推論する。他言語と異なるのは型(`type`)自体が値として扱われ、`comptime T: type`のように関数引数として渡せる点で、GenericsはRustのような専用構文ではなく、この`comptime`パラメータを使ったコンパイル時のダックタイピングオブジェクトの型そのものではなく、そのオブジェクトが実際に持つメソッドやプロパティ(振る舞い)で扱いを決める考え方。「アヒルのように鳴き、アヒルのように歩くならアヒルとみなす」という比喩に由来し、動的型付け言語でよく採用される。として実現される。 |
| OCaml | 静的型付け | Hindley-Milner型推論の実用化元となった言語で、型注釈はほぼ不要。 Hindley-Milner系の型推論を実用言語として広めた源流で、型注釈はほとんどの場面で省略できる。バリアント型(代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。)とパターンマッチが標準機能として組み込まれており、`option`型でNullの有無を型として表現する。RustやMoonBitの型システム・構文設計にも影響を与えている。 |
| F# | 静的型付け | OCaml譲りの強力な型推論を持ち、型注釈はほぼ不要。 OCamlと同系統のHindley-Milner型推論を持ち、型注釈はほとんどの場面で省略できる。判別共用体取りうる形が閉じた集合になっている型を、caseごとに異なるデータ(payload)を持たせて表現する仕組み。代数的データ型(ADT)のSum Typeに相当し、F#やOCaml(variant type)で使われる呼び方。パターンマッチと組み合わせて使うことで、全caseの網羅をコンパイラに検証させられる。(Discriminated Union)とパターンマッチが標準機能として組み込まれており、`Option<T>`でNullの有無を型として表現する。.NET上で動くため、C#など他の.NET言語ともシームレスに相互運用できる。 |
| Haskell | 静的型付け | 強力な型推論があり、型注釈はほぼ省略できる。 Hindley-Milner系の強力な型推論を持ち、型注釈はほとんどの場面で省略できる。代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。(ADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。)とパターンマッチが標準機能として組み込まれており、`Maybe`型でNullの有無を型として表現する。 |
| Erlang | 動的型付け | 変数は一度だけ束縛できる単一代入(single assignment)。 変数は一度束縛すると同じ値にしか再束縛できず、別の値を代入しようとすると`badmatch`エラーになる(Elixirの再束縛とは異なる)。型注釈の構文はなく、型の検査は実行時のパターンマッチとガード節(`is_integer`など)で行う。オプションのtypespec(`-spec`)をDialyzerという静的解析ツールで検査できるが、コンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。自体は検査しない動的型付け言語。 |
| Elixir | 動的型付け | 再代入ではなくパターンマッチによる束縛が基本。 `=`は代入ではなくパターンマッチによる束縛で、値そのものはイミュータブル。型注釈はオプションのtypespec(`@spec`)で後付けでき、Dialyzerという静的解析ツールで検査できるが、実行時の型検査自体は動的型付け。 |
| TypeScript | 静的型付け | コンパイル時に型検査するが、実行時に型情報は残らない。 型検査はコンパイル時のみで、JavaScriptとして実行される段階では型情報は消える。「静的に検査すること」と「実行時にも型情報を持つこと」が別問題であることを示す例。 |
| JavaScript | 動的型付け | 型注釈という概念自体がない。 TypeScriptの土台となる言語で、型注釈の構文がない。暗黙の型変換によって、意図しない挙動が実行時に初めて表面化することがある。 |
| PHP | 動的型付け | 関数の引数・戻り値には型宣言を書ける。 変数自体に型注釈を書く構文はないが、関数の引数・戻り値・プロパティには型宣言をつけられる。`declare(strict_types=1)`を宣言すると、型不一致時の暗黙変換をやめて厳格に検査できる。 |
| Ruby | 動的型付け | ダックタイピングオブジェクトの型そのものではなく、そのオブジェクトが実際に持つメソッドやプロパティ(振る舞い)で扱いを決める考え方。「アヒルのように鳴き、アヒルのように歩くならアヒルとみなす」という比喩に由来し、動的型付け言語でよく採用される。が基本。 変数に型注釈を書く構文はなく、「そのメソッドが呼べるかどうか」で型を扱うダックタイピングオブジェクトの型そのものではなく、そのオブジェクトが実際に持つメソッドやプロパティ(振る舞い)で扱いを決める考え方。「アヒルのように鳴き、アヒルのように歩くならアヒルとみなす」という比喩に由来し、動的型付け言語でよく採用される。が基本。RBSやSorbetのような外部ツールで型を後付けすることもできるが、言語コア自体には型注釈がない。 |
| Python | 動的型付け | 値は型を持つが、検査は主に実行時。 変数自体には型がなく、値の側に型が紐づく。型の不整合は、その値を実際に使った時点で初めて顕在化する。 |
トレードオフ
静的型付けの利点
- 実行前に問題を発見できる: 特定のコードパスを実際に通らないと分からない型の不整合を、コンパイル時に検出できる。
- コードの意図を表現できる:
fn calculateAge(birthday: Date) -> Intのように書かれていれば、関数が何を受け取り何を返すのかがコードから読み取れる。 - ツールによる支援を受けやすい: コンパイラが型を把握しているため、補完・定義ジャンプ・リファクタリングなどのIDE支援を受けやすい。
- 大規模な変更に強い: 引数や戻り値の型を変えたとき、影響範囲をコンパイラが機械的に発見できる。
静的型付けの欠点
- コンパイルに時間がかかる: 実行前に型検査というステップが挟まるため、コードを書いてから結果を確認するまでのフィードバックループが動的型付けより長くなりやすい。プロジェクトが大きくなるほど、この時間は無視できなくなる。