← melang

型注釈の位置

Last Updated: 2026-09-28 02:00

結論

melangでは変数宣言にpostfix方式(name: Type)を採用し、変数名を先に読ませる設計にする。一方、関数の引数・戻り値は型推論に頼らず常に明示する。

// 変数宣言: 型注釈は省略できる(推論に任せる)
val age: Int = 20
val age = 20

// 関数シグネチャ: 引数・戻り値は常に明示する
fun greet(name: String): String {
    return name
}

理由は次の2点である。

型注釈の位置という構文レベルの話は、型推論の範囲(どこまで省略できるか)や、Generics・Null許容型の表現方法とは独立した論点であり、それらは別のnoteで扱う。

他言語比較

型注釈(型宣言)の位置は、大きく「型が先か(prefix・前置)」「名前が先か(postfix・後置)」で分かれる。さらに、変数宣言・関数引数・戻り値でその扱いが変わる言語も多く、単純な二分法では捉えきれない。主要な言語がそれぞれの場面でどちらの方式を採用し、どこまで型推論に任せているかを比較する。

言語変数宣言関数引数戻り値型推論説明
Rustlet name: Typename: Type-> Type変数のみ強い
MoonBitlet name : Typename : Type-> Type変数のみ強い
Swiftlet name: Typename: Type-> Type変数のみ強い
Kotlinval name: Typename: Typefun f(): Type変数は強い/戻り値は式本体のみ
JavaType nameType nameType f()varのみ(ローカル)
Scalaval name: Typename: Typedef f(): Type変数は強い/戻り値は省略可(非推奨)
C#Type nameType nameType f()varのみ(ローカル)
C++Type nameType nameType f() / auto f()->Typeautoのみ(ローカル)
CType nameType nameType f()なし
Govar name Typename Typefunc f() Type:=のみ(ローカル)
Zigconst name: Typename: Typefn f() Type変数のみ強い
OCamllet name : Type(name : Type): Type非常に強い(ほぼ不要)
F#let name : Type(name : Type): Type非常に強い(ほぼ不要)
Haskellname :: Type(別行)A -> B の一部A -> B の一部非常に強い(ほぼ不要)
Erlang型注釈なし-specで別行(分離)-specで別行(分離)なし(Dialyzerが検査)
Elixir型注釈なし@specで別行(分離)@specで別行(分離)なし(Dialyzerが検査)
TypeScriptlet name: Typename: Type(): Type変数・戻り値は強い/引数は必須
JavaScript型注釈なし型注釈なし型注釈なし概念自体がない
PHP型注釈なしType $name: Typeなし(明示のみ)
Ruby型注釈なし型注釈なし(RBSで分離)型注釈なし(RBSで分離)なし(動的型付け)
Pythonname: Typename: Type-> Type型ヒントは任意(実行時は無視)

トレードオフ

Type-first(prefix)

Name-first(postfix)

シグネチャ方式(Haskell/OCamlの独立した型宣言)

型推論の範囲

ローカル変数のみ推論を許す言語(Rust, Kotlin, Swift, Java, C#, Zigなど)が多数派で、関数シグネチャは公開契約として明示を要求する。Haskell/OCamlのように関数シグネチャまで推論できる言語もあるが、実務上は可読性のためにシグネチャを明示する慣習が根付いている。型注釈の位置(prefix/postfix)と型推論の強さ(どこまで省略できるか)は独立した設計軸であり、postfix系の言語同士でも推論の範囲は言語ごとに異なる。

以上を踏まえ、melangでは変数宣言をpostfix方式かつ型推論を許容し、関数シグネチャは明示必須とすることで、可読性(名前を先に読む)と契約の明確さ(公開APIは常に型が分かる)を両立させる。