1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なぜ現代のプログラミング言語は三項演算子を採用しないのか

Posted at

表紙

プログラミングをしていると、私たちはよく条件判断を行い、その結果に基づいて異なるコードブロックを実行することがあります。多くのプログラミング言語において、最も一般的な書き方は三項演算子(Ternary Operator)です。しかし、Python では三項演算子がサポートされていません。奇遇なことに、人気の新興言語である Go や Rust も同様にサポートしていないのです!

では、なぜ Python は三項演算子を採用していないのでしょうか?この記事では、Python が条件分岐構文をどのように設計したかを中心に解説し、なぜ現在の独特な実装方式を選んだのかを解き明かします。また、他の言語が従来の三項演算子を採用しなかった理由についても考察します。

三項演算子とは?

三項演算子とは、一般的に「?:」の形で表現される構文です。構文形式は以下の通りです:

condition ? expression1 : expression2

ここで、condition が真であれば expression1 が、偽であれば expression2 が選ばれます。

簡略的に書くと「a ? b : c」となり、「もし a が真であれば b、そうでなければ c」と読むことができます。

三項演算子は通常の if-else 構文の簡易表現であり、条件判断と値の代入を同時に行う場合に便利です。

// 通常の if-else
if (a > b) {
    result = x;
} else {
    result = y;
}

// 三項演算子による簡略表現
result = a > b ? x : y;

このような構文を採用している言語は多数あり、たとえば C、C#、C++、Java、JavaScript、PHP、Perl、Ruby、Swift などが挙げられます。まさにプログラミング言語における主流のデザインといえるでしょう(今でも現役です)。

この構文は非常に簡潔かつ効率的であり、(初見でなければ)読みやすさも高いため、多くのプログラマーに好まれています。

しかし、この構文にも欠点がないわけではありません。Python はこの構文に挑戦した最も有名な言語の一つです。以下では、なぜ Python が独自の道を選んだのかを見ていきましょう。

Python コミュニティの投票

Python は 1991 年にリリースされましたが、その後 15 年間は if-else 以外の条件式、つまり三項演算子のような構文をサポートしていませんでした。そして、2006 年に条件式が導入されるまで、コミュニティ内では長く複雑な議論が続きました。これは非常に苦労して設計された構文だったといえます。

当初、多くのユーザーから「if-then-else(三項)式を導入してほしい」という要望が出ていたため、2003 年 2 月に PEP 308 – 条件式 が提出されました。目的は、コミュニティの多数が支持する構文案を選び出すことでした。

すぐに、少数の「現状維持派」を除いて、いくつかの構文案が登場しました:

(1)記号による三項演算子の導入案

これは通常の三項演算子で、以下のような構文です:

<condition> ? <expression1> : <expression2>

この案は多くの支持を集め、実装コードを提出する開発者もいました。しかし、Guido(Python の設計者)は二つの反対理由を挙げました:1 つ目は、コロン(:)はすでに Python で多くの用途に使われており(実際にはあまり曖昧さはないとしても、疑問符との組み合わせによって問題になる可能性がある)、2 つ目は、C 系の言語に慣れていない人にとって理解が難しいという点です。

(2)既存および新しいキーワードを用いた構文案

then という新しいキーワードを導入し、既存の else と組み合わせた案です:

<condition> then <expression1> else <expression2>

この案の長所は、明確で分かりやすく、括弧が不要で、既存キーワードの意味を変更せず、文との混乱も起こりにくく、コロンの再利用も不要という点です。一方で、キーワード追加の実装コストが高いという欠点があります。

(3)その他の案

上記二つの大分類に入らない案で、支持率もそこまで高くはありませんでした。

(if <condition>: <expression1> else: <expression2>)
<condition> and <expression1> else <expression2>
<expression1> if <condition> else <expression2>
cond(<condition>, <expression1>, <expression2>)

特に注目されたのは (if <condition>: <expression1> else: <expression2>) で、これは通常の if-else 構文の平坦化された形で理解しやすい反面、括弧が必要でジェネレータ式と紛らわしくなりやすく、さらにインタプリタがコロンに特別な扱いを必要とするという欠点がありました。

また、<expression1> if <condition> else <expression2> という構文もあり、これは PEP-308 の初期案でも推薦されていました。しかし、「条件を先頭に置かない」スタイルに違和感を持つ人も多く、特に expression1 が長くなると条件部分が見落とされやすいという問題もありました。

当時の投票で挙がったすべての構文案は以下の通りです:

A.   x if C else y
B.   if C then x else y
C.   (if C: x else: y)
D.   C ? x : y
E.   C ? x ! y
F.   cond(C, x, y)
G.   C ?? x || y
H.   C then x else y
I.   x when C else y
J.   C ? x else y
K.   C -> x else y
L.   C -> (x, y)
M.   [x if C else y]
N.   ifelse C: x else y
O.   <if C then x else y>
P.   C and x else y
Q.   any write-in vote

全体として、開発者たちは何らかの形の if-then-else 式を導入したいと考えていましたが、投票の結果、どの案も決定的な支持を得るには至りませんでした。まとめると、意見が分かれた主な論点は次の通りです:

  • 記号を使用すべきか
  • キーワードを再利用すべきか
  • 丸括弧を使うべきか
  • 新しいキーワードを導入すべきか
  • 新しい構文を導入すべきか

票が分散しすぎたため、この PEP はその時点では却下されました。PEP には次のように書かれています:「Python の設計原則の一つは、進むべき道がはっきりしない時は現状維持を選ぶことである。」

and-or を使った条件選択の問題点

この投票イベントは 2004 年 3 月に行われましたが、PEP が却下された後もこの話題の議論は収まりませんでした。というのも、多くの開発者が「if-else」よりも簡潔な方法を求めていたからです。

そして 2005 年 9 月、あるユーザーがメーリングリストで次の提案を行いました:Py3.0 における and と or 演算子のロジックを変更する提案。その内容は、andor の演算子を常にブール値を返すように単純化しようというものでした。つまり、最後に評価された値ではなく、真偽値そのものを返すという提案です。

この提案の背景には、「<condition> and <expression1> or <expression2>」という形式を使って条件選択を行おうとしたことがあります。しかし、この書き方は Python の他の言語とは異なる挙動を示すため、注意を怠るとバグの原因となるのです。

以下の例を見てください。これらの式の結果は何になると思いますか?

a = True and True or "Python"
b = True and False or "Python"

<condition> and <expression1> or <expression2> という構文においては、condition が偽の場合は直接 expression2 が評価され、結果として返されます。一方、condition が真の場合、まず expression1 が評価され、これが真であれば expression2 は評価されず、偽であれば expression2 が評価されます。

したがって、上記の例では a の値は "True"b の値は "Python" になります。

Python における真偽判定には特徴があります。たとえば、上記メールの筆者は expression1 に複素数 0+4i を使ったところ、この値の真偽判定が False と評価されてしまい、本来返されるはずの expression1 ではなく expression2 が返されるというバグに遭遇しました。

より良い方法がなかった当時は、この「and-or」形式が条件選択の一般的な書き方とされており、PEP-308 にもこの構文が言及されています。PEP では、「expression1 が偽のとき問題が起きやすく、この形式は醜くて分かりづらい」と明言されています。

このメールは再びコミュニティに条件選択構文についての議論を巻き起こしました。多くの有識者たちが議論に参加しました。

今の視点で分析すると、これは「開発者が if-else の現状に満足していなかったが、当時流行っていた and-or 構文も最適ではなかったため、Python に新たな公式構文を期待していた」という流れだと言えるでしょう。

独特な条件式構文

このメールスレッドでの議論が始まってから 10 日後、Guido van Rossum は最終的に新しい条件式構文の導入を決定しました。その構文とは:

X if C else Y

この決定により、PEP-308 は再開・更新され、翌年リリースされた Python 2.5 において正式に実装されました。

先述した通り、この構文に違和感を持つ人もいました。というのも、条件式である C を構文の最初に置かないため、直感的ではないと感じられるからです。

では、なぜ最終的にこの構文が選ばれたのでしょうか? これは最良の設計だったのでしょうか?

決定的な理由は、Guido 自身の判断です。というのも、1 年半前の投票では多数派意見がまとまらなかったため、彼は BDFL(終身仁慈独裁者)としての権限を行使し、自らが最も適切と考える案を採用しました。

X if C else Y は非常に理解しやすく、読みやすい構文です。この構文は「明示は黙示に勝る(Explicit is better than implicit)」という Python の設計哲学を体現しています。複雑な記号を使うのではなく、「if」や「else」といった口語的で直感的なキーワードを用いています。これは、Python が &&|| の代わりに andor を使っているのと同じ思想です。

この構文の順序に慣れるまでには少し時間がかかるかもしれませんが、実は非常に多くの利点があります。まず、「if」および「else」の既存キーワードだけを再利用するため、「then」や「when」などの新しいキーワードを導入する必要がありません。また、(if <condition>: <expression1> else: <expression2>) のような冗長な構文とも異なり、簡潔です。

さらに、Guido は X if C else Y の有効性を検証するため、Python の標準ライブラリ全体を調査しました。その結果、C and X or Y という記述はすべて X if C else Y に置き換えることが可能であると確認されました。その詳細は 標準ライブラリの調査報告 にも記載されています。

このようにして振り返ってみると、Python が三項演算子 ?: を採用しなかったのは、「明確さ」や「直感的理解のしやすさ」といった Python の設計方針に合わなかったからであり、最終的に X if C else Y という構文を導入したのは、and-or 構文の危険性を解消し、より明確で読みやすい記述を提供するためだったのです。

全体として、Python の設計者たちは可読性と保守性を非常に重視しており、三項演算子を採用しない代わりに新しい条件式構文を慎重に設計しました。それは、オープンな議論を経て、慎重な評価とバランス感覚のもとで導き出された選択だったのです。

Go や Rust が三項演算子をサポートしない理由

Python の設計理由を見てきたところで、次に「反対陣営」に属する 2 つの人気言語を見てみましょう。

まずは Go 言語から。公式 FAQ には明確に次のような問いが記されています:

「なぜ Go には ?: 演算子がないのですか?」

Go 言語では三項演算子 ?: がサポートされておらず、代わりに通常の if-else を使うことが推奨されています。ドキュメントの説明は簡潔で、次のように記されています:

Go に ?: 演算子はありません。なぜなら、設計者たちはこの演算子が理解しづらい複雑な式を生む場面をよく目にしてきたからです。たとえ if-else 構文が少し長くなったとしても、はるかに明確で読みやすいのです。条件分岐のための構文は 1 種類で十分です。

次に Rust 言語について見てみましょう。Rust の公式ドキュメントには三項演算子をサポートしない理由について明記されていませんが、調査を進めると興味深い事実が判明しました。

実は 2011 年 6 月、Rust ではかつて三項演算子(#565)を導入していたことがありました。しかし、半年後にその機能は「不要」と判断され、削除されました(#1698、#4632)。

では、なぜ Rust において三項演算子が「不要」だったのでしょうか?
それは、Rust における if が「ステートメント(文)」ではなく、「式(expression)」として設計されているからです。つまり、if の結果をそのまま変数に代入することができるのです:

// 条件が真なら 5、そうでなければ 6
let number = if condition { 5 } else { 6 };

この構文は非常にシンプルで直感的です。誰もが知っている if-else をそのまま代入に使えるというのは便利であり、わざわざ三項演算子を導入する意味がほとんどなくなるのです。

Rust では中括弧 {} を使ってコードブロックを明示的に区切ります。そのため、次のようにブロック内に複数の式を記述したり、改行を含めたりすることもできます:

let x = 42;
let result = if x > 50 {
    println!("x は 50 より大きい");
    x * 2 // これは式であり、result に代入される値
} else {
    println!("x は 50 以下");
    x / 2 // これも式で、代入される
};

このような使い方は、Python では実現できません。決定的な違いは、Rust の if は「式(expression)」であり、Python の if は「文(statement)」であるという点にあります。

この 2 つの概念の違いは次の通りです:

  • 式(expression):変数・定数・演算子などから構成され、値を返すコード片。他の式や文の中で使うことができる。
  • 文(statement):ある処理を実行するための命令。たとえば代入文、条件文、ループ文など。値を返さず、他の文の中で使うことはできない(または返す値は無視される)。

Rust 以外にも、if を式として扱える言語があります。たとえば Kotlin、Scala、F#、Swift などです。理論的には、これらの言語も三項演算子を必要としないといえます。
(ちなみに Swift は例外で、三項演算子も備えています。Kotlin の ?: 演算子も特徴的で、これは null 合体演算子です。例:val result = a ?: b は「a が null でなければ a、null なら b」を意味します)

このように、言語設計のレベルで「if を式とするか文とするか」の違いがあるため、「三項演算子を導入すべきかどうか」という問いに対する各言語のアプローチにも大きな差が生まれるのです。この違いを理解すれば、言語ごとの設計方針に対する理解も深まるでしょう。

結論:なぜ一部の言語は三項演算子を採用しないのか?

あらためて、この記事の問いに戻りましょう:

なぜ一部のプログラミング言語は、主流の三項演算子 ?: を採用しないのでしょうか?

確かに、?: は非常に簡潔で便利な構文です。しかし、記号による表現は抽象度が高く、特に初心者にとっては読みやすさに欠けることがあります。Python のように「明確で読みやすい」コードを重視する言語にとっては、これは大きな欠点となります。

また、言語の設計思想や使用者の習慣によって、何を「良い構文」とするかは異なります。

  • Python?: は使わず、X if C else Y を公式構文として導入。and-or による危険な表現を解消しつつ、明確な構文を提供。
  • Go:公式に ?: を採用しない方針を明示。理由は「簡潔さよりも明確さを優先する」ため。
  • Rust:一度は導入した ?: を廃止。理由は、if 自体が式として使えるため、三項演算子が不要だったから。

このように、それぞれの言語がそれぞれの哲学に基づいて、三項演算子の採用を見送っています。


私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。

Leapcell

Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:

複数言語サポート

  • Node.js、Python、Go、Rustで開発できます。

無制限のプロジェクトデプロイ

  • 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

比類のないコスト効率

  • 使用量に応じた支払い、アイドル時間は課金されません。
  • 例: $25で6.94Mリクエスト、平均応答時間60ms。

洗練された開発者体験

  • 直感的なUIで簡単に設定できます。
  • 完全自動化されたCI/CDパイプラインとGitOps統合。
  • 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

簡単なスケーラビリティと高パフォーマンス

  • 高い同時実行性を容易に処理するためのオートスケーリング。
  • ゼロ運用オーバーヘッド — 構築に集中できます。

ドキュメントで詳細を確認!

Try Leapcell

Xでフォローする:@LeapcellHQ


ブログでこの記事を読む

1
2
0

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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?