← melang

名前付き引数

Last Updated: 2026-09-28 02:00

結論

melangでは、関数呼び出し時に名前付き引数を必須とする。すべての引数に、定義側の引数名をそのまま書かせる。

fn resize(width: Int, height: Int) -> Image { ... }

resize(width: 10, height: 20)  // OK
resize(10, 20)                 // NG: 位置引数だけの呼び出しは禁止

名前付き引数を必須にする理由

Kotlin・Python・C#などの多くの言語は、名前付き引数を「書いてもよい」機能として提供し、位置引数だけの呼び出しも許す。この場合、名前を書くかどうかは呼び出す側の裁量に委ねられ、同じ関数でも呼び出し箇所によって読みやすさが変わる。

melangは、コードを読んだときに処理の意味を追いやすいことを言語設計の前提としており(「I/O の明示」のnoteと同じ方針)、引数の意味も呼び出し箇所ごとの裁量に委ねず、常に呼び出し側のコードから読み取れるようにしたい。特に、同じ型の引数が複数並ぶ場合(resize(width, height)のような呼び出し)は、位置だけでは取り違えに気づけない。

resize(20, 10)                 // widthとheightが逆でも、見た目だけでは気づけない
resize(width: 20, height: 10)  // 名前を書けば、取り違えが呼び出し側から見える

名前付き引数を必須にすると、関数のシグネチャが変わったとき(引数の追加・順序変更)にも、呼び出し側の意図(どの値をどの引数に渡しているか)が名前によって保たれる。位置引数だけの呼び出しでは、引数を追加したり順序を変えたりすると、既存の呼び出し箇所が意図せず別の引数に値を渡してしまう危険がある。

検討した案

Swiftのように、定義側の引数名とは別に呼び出し用のラベル(外部引数名)を持たせる設計も検討したが、採用しなかった。ラベルと引数名が分かれると、定義を読むときと呼び出しを読むときで異なる名前を覚える必要があり、「名前で意味を明示する」という目的に対して複雑さが増える。melangでは、定義側の引数名をそのまま呼び出し側の名前として使う、Kotlinに近い一枚看板の設計にする。

また、Kotlin・C#のように「位置引数と名前付き引数を併用できる」設計も検討したが、採用しなかった。併用を許すと、同じ関数でも呼び出し箇所によって名前を書いたり書かなかったりでき、必須にする意味が薄れる。

このnoteで決めないこと

他言語比較

名前付き引数の設計は、大きく次の4つに分かれる。

言語名前付き引数呼び出し時の書き方位置引数との併用説明
Rustなし位置引数のみ-(構造体で代替)
MoonBitあり(labelled arguments)foo(~width=10, ~height=20)併用可(ラベルなし引数と混在可)
Swiftあり(既定で必須)foo(width: 10, height: 20)基本併用しない(ラベルが既定)
Kotlinありfoo(width = 10, height = 20)併用可
Javaなし位置引数のみ-
Scalaありfoo(width = 10, height = 20)併用可
C#ありFoo(width: 10, height: 20)併用可(名前付きは位置引数の後ろ)
C++なし位置引数のみ-
Cなし位置引数のみ-(構造体で代替)
Goなし位置引数のみ-
Zigなし(struct引数で代替)foo(.{ .width = 10, .height = 20 })-(構造体のフィールドデフォルト値と併用)
OCamlあり(labeled arguments)foo ~width:10 ~height:20併用可(ラベルなし引数と混在可)
F#あり(メンバーメソッドのみ)obj.Foo(width = 10, height = 20)併用可(メンバーメソッドに限る)
Haskellなし位置引数のみ-(レコード構文で代替)
Erlangなし(proplist/mapで代替)foo([{width, 10}, {height, 20}])-(最後の引数としてまとめて受け取る)
Elixirなし(キーワードリストで代替)foo(width: 10, height: 20)(実体はキーワードリスト)-(最後の引数としてまとめて受け取る)
TypeScriptなし(関数引数としては)foo({ width: 10, height: 20 })-(オブジェクト引数として1つにまとめる)
JavaScriptなしfoo({ width: 10, height: 20 })-
PHPあり(PHP 8.0〜)foo(width: 10, height: 20)併用可
Rubyあり(keyword arguments)foo(width: 10, height: 20)併用可(定義側で明確に区別)
Pythonありfoo(width=10, height=20)併用可

比較の観点は次の通りである。

  1. 名前付き引数が言語機能として存在するか。
  2. 呼び出し時に名前を書くことが必須か、任意か。
  3. 名前付き引数と位置引数を併用できるか。
  4. 名前を書く場合、順序を入れ替えられるか。
  5. 「名前付き引数がない言語」が、構造体・オブジェクト・Builder等で何を代替しているか。

Kotlin・C#・Scala・Python・PHP・Rubyは、名前付き引数を任意の機能として提供する。デフォルト引数と組み合わせ、オプションが多い呼び出しを読みやすくする目的で使われることが多いが、名前を省略した呼び出しも許されるため、同じ関数でも呼び出し箇所によって読みやすさが変わる。

Swiftは、引数ラベルが呼び出し側に既定で現れる数少ない言語である。省略したい場合は定義側で_を明示する必要があり、「ラベルを書かない」ことのほうが特別な指定になる。OCaml・MoonBitのラベル付き引数も、ラベルを省略する構文自体を持たない。melangが目指す「必須」の設計は、この3言語に最も近い。

Rust・C++・Go・Java・Haskell・Zigは名前付き引数を持たず、意味を明示したい場合は構造体・オブジェクト(Go・Rust)やBuilderパターン(Java)、レコード構文(Haskell)で代替する。Zigはフィールドにデフォルト値を持てるstructを、型名を省略できるanonymous struct literal(.{ .field = value })で渡す慣習を持ち、省略したフィールドはデフォルト値に委ねられる。これらは、引数の意味を「引数そのものの書き方」ではなく「渡す値の型」に持たせる設計であり、melangが目指す「呼び出しのコードだけで意味が分かる」方向とは異なる。

TypeScript・JavaScriptは、オブジェクトを1つの引数として渡し、分割代入で受け取るパターンで名前付き引数に近い書き心地を実現する。Elixirのキーワードリストも見た目は似ているが、実体は「最後の引数として1つのリストを受け取る」設計であり、個々の引数名をコンパイラや処理系が検証するわけではない。

トレードオフ

名前付き引数を持たない設計(Rust, C++, Go, Java, Haskellなど)

名前付き引数を任意で使える設計(Kotlin, C#, Scala, Python, PHP, Rubyなど)

名前付き引数を既定で必須にする設計(Swift、ラベル付き引数を持つOCaml・MoonBit)

構造体・オブジェクトで代替する設計(Rust・Go・Javaの構造体/Builder、TypeScript/JavaScriptのオブジェクト引数)

melangの選択

melangは、Swift・OCaml・MoonBitに近く、名前付き引数を任意ではなく必須にする。定義側の引数名をそのまま呼び出し側の名前として使い、Swiftのような別ラベルは持たない。Kotlin・C#のような「任意」の設計は採用せず、位置引数だけの呼び出しを許さない。

意味を明示するコストよりも、呼び出しを読むだけで意味が分かることを優先する。