if 文/式
Last Updated: 2026-09-30 12:00
結論
melangでは、ifを手続き処理として使う場合と値を返す式として使う場合で、elseの扱いを分ける。
- 手続き処理の
if: 許可する。ただしelseは禁止する。 - 式としての
if:elseを必須とする。
// 手続き処理: elseは書けない。条件に該当しないケースを先に処理してearly returnする
if !condition {
return handle()
}
process()
// 式: elseが必須
x = if condition {
foo()
} else {
bar()
}
手続き処理でelseを禁止する代わりに、次のように書くことになる。
// NG: 手続きのelse
if condition {
process()
} else {
handle()
}
// OK: 該当しないケースを先に処理する
if !condition {
return handle()
}
process()
つまりelseは、「手続きのもう一方を実行するための構文」ではなく、値の分岐を完成させるための構文として位置付ける。
狙い
- 手続き処理をネストさせず、early returnに寄せる。
- 正常系を関数の後半にまっすぐ書きやすくする。
elseの意味を、値の分岐に限定する。- 式と手続きの違いを、syntaxから読み取りやすくする。
このnoteで決めないこと
次の論点は、このnoteでは決めず、別の機会に扱う。
else ifをどう扱うか。- 式としての
ifで、各branchが値を返すときの構文(最後の式が値になるかなど)。 - early returnだけでなく、
break/continueなどを含む場合の扱い。 - 式としての
ifを、statement positionで使った場合の扱い。 match/ pattern matchingとの役割分担。
他言語比較
ifとelseの扱いは、「ifが式か文か」と「elseが必須か任意か」で整理できる。Rust・Kotlin・Scala・Zig・OCaml・Haskellなどはifを式として扱い、Java・C#・C・C++・Go・Python・JavaScript・TypeScript・PHPなどはstatementとして扱って、値の分岐には三項演算子などを別に用意する。
| 言語 | `if`は式か | `else`の必須性 | 手続きの`else` | 説明 |
|---|---|---|---|---|
| Rust | `if`自体が式 | 任意(値を使うなら必須) | `else`も書ける | `if`は式。`else`がなければ`()`を返すので、手続きと値の`if`が同じ構文になる。 `if`は式で、`else`がないと型は`()`になる。そのため値として使う場合は`else`が事実上必須になるが、構文は手続きの`if`と同じである。`else`の使い方に制限はなく、`if let ... else`や`let ... else`(早期脱出用)も用意されている。 |
| MoonBit | `if`自体が式 | 任意(値を使うなら必須) | `else`も書ける | `if`は式。`else`を省略すると`Unit`になり、値を返す場合は`else`が必要になる。 Rustに近く、`if`は式として扱われる。`else`を省略した`if`は`Unit`を返すため、値を使う場合だけ`else`が必要になる。手続きの`else`も禁止されていない。 |
| Swift | statement(5.9以降は式としても可) | 式では必須、statementでは任意 | `else`も書ける(`guard`もある) | 通常の`if`はstatement。値を返す`if`式では`else`が必須で、早期脱出用に`guard ... else`がある。 `if`はstatementだが、Swift 5.9から代入・return・クロージャの値として使える`if`式が加わった。式として使う場合は`else`が必須で、各branchは単一の式である必要がある。`guard`は`else`が必須で、`return`・`break`・`continue`・`throw`などでスコープを抜けなければならない。早期脱出という思想はmelangに近いが、`guard`という別の構文を足している。 |
| Kotlin | `if`自体が式 | 式として使うなら必須、statementでは任意 | `else`も書ける | `if`は式。値として使うときは`else`が必須で、`else`なしの`if`はstatementとしてのみ使える。 値として使うときだけ`else`が必須になる点が、melangの設計に近い。一方で、手続き的な`if ... else`も許可されているため、`else`の用途は制限されていない。 |
| Java | statementのみ(三項演算子・switch式で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐には三項演算子`?:`やswitch式を使う。 `if`はstatementで、値を返せない。値の分岐は三項演算子(両側が必須)かswitch式で書く。「手続きは`if`、値は別の構文」と分かれているが、手続きの`else`は自由に使える。 |
| Scala | `if`自体が式 | 任意(なければ`Unit`) | `else`も書ける | `if`は式。`else`がなければ`Unit`を返し、`else if`もそのまま式として連なる。 `if`は式で、`else`を省略すると値は`Unit`になる。値として使うつもりで`else`を忘れても型が広がるだけでエラーにならない場合がある。Scala 3では波括弧を省略するインデント構文も使える。 |
| C# | statementのみ(三項演算子・switch式で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐には条件演算子`?:`やswitch式を使う。 Javaと同様に、`if`はstatementで値を返せない。値の分岐は条件演算子と、パターンを扱えるswitch式で書く。 |
| C++ | statementのみ(三項演算子で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐は三項演算子`?:`で書く。 `if`はstatementで、値の分岐は三項演算子を使う。`if constexpr`はコンパイル時の分岐であり、値を返す式ではない。 |
| C | statementのみ(三項演算子で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐は三項演算子`?:`で書く。 `if`はstatementで、値を返す分岐は三項演算子で書く。`else`の使い方に制限はない。 |
| Go | statementのみ(三項演算子なし) | 任意 | `else`も書ける(慣習はearly return) | `if`はstatementで、三項演算子もない。`else`を避けたearly returnが慣習になっている。 `if`は式ではなく、三項演算子もない。値の分岐は変数を宣言してからstatementで代入する。一方で、`return`で終わるbranchのあとに`else`を書かないという慣習があり、linterも指摘する。melangが目指す「手続きはearly return」に近い文化を持つが、言語としては強制していない。 |
| Zig | `if`自体が式 | 式として使うなら必須、statementでは任意 | `else`も書ける | `if`は式。値として使うときは`else`が必須で、statementとしては`else`なしで書ける。 `if`は式で、値として使う場合は`else`が必須になる。この点はKotlinと同じである。手続きの`else`は禁止されていない。 |
| OCaml | `if`自体が式 | 任意(なければthen側が`unit`である必要がある) | `else`も書ける | `if`は式。`else`を省略できるのは、then側が`unit`のときだけ。 `if`は式で、`else`を省略した場合はthen側が`unit`でなければならない。型システムが「値を返す`if`は`else`が必須」を保証する。 |
| F# | `if`自体が式 | 任意(なければthen側が`unit`である必要がある) | `else`も書ける | `if`は式。`else`を省略できるのは、then側が`unit`のときだけ。 OCamlと同じく、`if`は式で、`else`を省略できるのはthen側が`unit`のときだけである。型によって「値を返す`if`は`else`が必須」が保証される。 |
| Haskell | `if`自体が式 | 常に必須 | 手続きの`if`は`when`などで代替 | `if`は式で、`else`は常に必須。`else`なしで副作用を実行するには`when`を使う。 `if`は式で、`else`を省略できない。条件付きの副作用は`Control.Monad`の`when`・`unless`で表すため、「手続きの`if`」と「値の`if`」が構文ではなくライブラリ関数で分かれている。 |
| Erlang | `if`自体が式(ガード式のみ) | `else`キーワードなし(`true ->`で代替) | `true ->`節で書ける | `if`は式だが`else`はなく、ガード式の節で分岐する。どの節にも一致しないと実行時エラーになる。 `if`は式で、`else`という構文はない。節の末尾に`true ->`を置いてelse相当を表す。条件にはガード式しか書けないため、実際には`case`やパターンマッチによる関数節の分岐が主に使われる。 |
| Elixir | `if`自体が式(マクロ) | 任意(なければ`nil`) | `else`も書ける | `if`は式(マクロ)。`else`を省略すると`nil`を返す。分岐の主役は`case` / `cond` / 関数節。 `if`は式で、`else`を省略すると`nil`が返る。`else`忘れが`nil`という値として現れるため、気づきにくい場合がある。多分岐にはパターンマッチを使う`case`や`cond`、関数の節が主に使われる。 |
| TypeScript | statementのみ(三項演算子で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐は三項演算子`?:`で書く。 JavaScriptと同じく、`if`はstatementで値を返せない。値の分岐は三項演算子で書き、ネストすると読みにくくなりやすい。 |
| JavaScript | statementのみ(三項演算子で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐は三項演算子`?:`で書く。 `if`はstatementで、値の分岐には三項演算子を使う。式として書ける`do`式の提案はあるが、標準化されていない。 |
| PHP | statementのみ(三項演算子・match式で代替) | 任意(三項演算子は必須) | `else`も書ける | `if`はstatement。値の分岐は三項演算子`?:`やmatch式で書く。 `if`はstatementで、値の分岐は三項演算子か、PHP 8.0で加わったmatch式を使う。`else if`と`elseif`の2つの書き方がある。 |
| Ruby | `if`自体が式 | 任意(なければ`nil`) | `else`も書ける | `if`は式。`else`を省略すると`nil`を返し、後置`if`も書ける。 `if`は式で、`else`を省略すると条件が偽のときに`nil`が返る。`unless`や後置`if`など、条件を書く方法が多い。 |
| Python | statementのみ(条件式で代替) | 任意(条件式は必須) | `else`も書ける | `if`はstatement。値の分岐は条件式`a if cond else b`で書き、この形では`else`が必須。 `if`はstatementで、値を返す分岐は条件式(`a if cond else b`)として別に用意されている。この形では`else`を省略できず、「値の分岐は`else`が必須」という点はmelangと同じ考え方になる。ただし、手続きの`else`は自由に使える。 |
Rust・Kotlin・Swift・Zigとの違い
Rustはifを式として扱い、elseは任意である。elseがない場合は()が返るため、手続き的なifと値を返すifが同じ構文で表現される。
Kotlin・Zigもifを式として扱い、値として使う場合はelseが必要になる。elseがないifはstatementとしてのみ使える。この点はmelangの設計に近い。ただし、どちらも手続き的なif ... elseを許可するため、melangはさらにelseの用途を制限する。
Swiftの通常のifはstatementで、elseは任意である。guardはelseが必須で、return / break / continue / throwなどでスコープから脱出しなければならない。「手続き的なelseをなくして、early returnを促す」というmelangの思想は、guard ... elseに近い。ただしmelangでは新しい構文を追加せず、通常のifのelseを制限することで実現する。
Go・Haskellが示す2つの方向
Goはifがstatementで、三項演算子もない。一方で、returnで終わるbranchのあとにelseを書かないという慣習があり、early returnが文化として定着している。melangは、この慣習を言語仕様として強制する。
Haskellはifが式で、elseが常に必須である。条件付きの副作用はwhenのような関数で表し、手続きのifと値のifが別の仕組みに分かれている。「値のifはelseが必須」という点は同じだが、melangは手続きのifを構文として残す。
トレードオフ
ifが式で、elseが任意の設計(Rust, Scala, Ruby, Elixirなど)
- 利点: 手続きも値も同じ
ifで書ける。構文が1つで済み、覚えることが少ない。 - 欠点:
elseを省くと()/Unit/nilが返り、値として使うつもりのifでelseを書き忘れても気づきにくい場合がある。手続きのelseも自由に書けるため、ネストが深くなり得る。
ifはstatementで、値の分岐は別の構文を使う設計(Java, C#, C, C++, Python, JavaScript, TypeScript, PHPなど)
- 利点: 手続きの
ifと、値の三項演算子・条件式が構文で分かれている。 - 欠点: 値の分岐に別の構文を覚える必要がある。三項演算子はネストすると読みにくく、複数行の処理を書けない。手続きの
elseには制限がない。
ifが式で、値として使うときだけelseが必須な設計(Kotlin, Swift 5.9以降のif式, Zig, OCaml, F#, Haskellなど)
- 利点: 値の分岐が不完全なまま使われることを、コンパイル時に防げる。
- 欠点: 手続きの
elseは自由に使えるため、elseによるネストを避けたい場合は、書き手が意識して early return を選ぶ必要がある。
melangの選択
melangは、手続きのelseを禁止し、式としてのifではelseを必須にする。
- Pros: 手続き処理がネストせず、early returnに寄る。正常系が関数の後半にまっすぐ書ける。
elseが付いていれば値の分岐であると分かり、式と手続きの違いをsyntaxから読み取れる。値の分岐でelseを書き忘れることがない。 - Cons: 2つの処理が対等に並ぶ分岐(どちらも正常系のような場合)も、片方を先に処理して抜ける形に書き直す必要がある。関数の途中で分岐を閉じたいだけの場面でも、
returnやbreak/continueが使えなければ書きにくい。elseの意味が手続きと値で分かれるため、他言語から来た人には最初は違和感がある。
書き手にelseの選択肢を与えるよりも、early returnに寄せた読みやすさと、式と手続きの区別を優先する。