0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

`&&` は「かつ」ではない

0
Posted at

&& はなぜ言語によって「Boolean」を返したり「オペランドの値」を返したりするのか — 歴史的経緯の調査

a && b という式の値は、言語ファミリーによって異なる。

  • C / Java 系: 真偽値を返す。C では int10、Java では booleantruefalse
  • Lisp / Scheme / JavaScript 系(Python、Ruby、Perl も同様): オペランドの値そのものを返す。"foo" && 4242 であり、nil && 42nil である。
/* C */
int x = (5 && 3);      /* x == 1。5 でも 3 でもない */
// JavaScript
let x = 5 && 3;        // x === 3
let y = 0 || "default"; // y === "default"
;; Common Lisp
(and 5 3)   ; => 3
(or nil 7)  ; => 7

この違いは偶然ではなく、独立に成立した 2 つの設計系譜が背後にある。一次資料(Lisp 1.5 マニュアル、Dennis Ritchie による C 開発史の論文、MacLisp マニュアル、ECMAScript 初版仕様)を確認した結果を以下にまとめる。調査の過程で分かった重要な事実を先に述べておく: オペランドの値を返す仕様は、Lisp の最初からの仕様ではない。初期の Lisp (Lisp 1.5、1962年) の and/or は真偽値を返す述語だった。


1. 共通の祖先: McCarthy の条件式と短絡評価

返り値の話に入る前に、両系譜に共通する「短絡評価」(左から評価し、結果が確定したら残りを評価しない)の起源を確認しておく。

短絡評価の概念は John McCarthy の条件式 (conditional expression) に遡る。McCarthy は 1957〜58 年、IBM 704 上の FORTRAN 向けリスト処理ライブラリ FLPL でチェスの指し手の合法性判定を書いた際、XIF(M, N1, N2) という関数では両方の枝が常に評価されてしまう(未定義の枝を評価するとエラーになる)ことを問題視し、選ばれた枝だけを評価する条件式の必要を認識した。これが Lisp の cond になり、McCarthy は ALGOL 60 の設計論議にも条件式を提案して if-then-else 式として採用された(McCarthy, "History of Lisp", 1978)。

(and a b)(cond (a b) (t nil)) の、(or a b)(cond (a a) (t b)) の短縮形とみなせる。つまり Lisp 系では and/or は最初から「論理演算」ではなく「条件分岐の略記」だった。この点が後の返り値の設計に効いてくる。

なお、短絡評価は ALGOL 系では長く標準ではなかった。ALGOL 60 の は両オペランドを評価する通常の演算子であり、短絡版は後に "cand / cor"(conditional and/or)や「McCarthy 評価」という名前で別途議論され、Ada では and then / or else として明示的な別演算子になった。C が && || を短絡と定めたことで、C 系言語では短絡がデフォルトになった。


2. Lisp 系: 「述語」から「値を返す条件分岐」への変化

2.1 Lisp 1.5 (1962) の and/or は真偽値を返していた

Lisp 1.5 Programmer's Manual (McCarthy ほか, MIT Press, 1962) の Appendix A、p.58 "Logical Connectives" には次のようにある。

and[x1;x2...;xn] : FSUBR predicate
The arguments of and are evaluated in sequence, from left to right, until one is found that is false, or until the end of the list is reached. The value of and is false or true respectively.

or[x1;x2...;xn] : FSUBR predicate
The arguments of or are evaluated in sequence from left to right, until one is found that is true, or until the end of the list is reached. The value of or is true or false respectively.

短絡評価はすでにあるが、値は true / false(実体は *T* / NIL)に正規化されている。「or は最初に真だったオペランドの値を返す」という現代の Lisp の仕様は、ここには存在しない。

2.2 MacLisp 世代 (1960年代後半〜) でオペランド値を返す仕様になった

PDP-6 Lisp (1966) を起源とする MacLisp の系統では、仕様が変わっている。MacLisp のリファレンス(Pitman による The Revised Maclisp Manual)は次のように定めている。

AND: If none of the arguments evaluate to NIL, the AND returns the value of its last argument.

OR: If an argument evaluates to something other than NIL, the OR immediately returns that value without evaluating any remaining arguments.

Interlisp (BBN、1970年代のもう一方の主要方言) も同じ仕様を採り、両方言を統合した Common Lisp (1984) で「or は最初の非 NIL の値を返し、and は最後の式の値を返す」が標準化された。Scheme も同様で、R7RS の and/or は最後(または最初の真)の式の値を返す。

2.3 なぜ変わったのか

この変更が「誰の提案でいつ」入ったかを記した文書は見つからなかった(PDP-6 Lisp のメモ AI Memo 116 前後のどこかである)。ただし、変更が合理的だった理由は Lisp の意味論から説明できる。

  1. 「偽」が専用の型ではない。 Lisp では NIL が偽であり、NIL 以外のあらゆるオブジェクトが真である(Common Lisp で言う generalized boolean)。真偽の判定に型変換が要らないので、「真であるオブジェクトそのもの」を返しても真偽値として完全に機能する。
  2. 情報を捨てる理由がない。 (or x y)T に正規化すると「どの値が真だったか」という情報が失われる。オペランドを返す仕様は、真偽値としての用途を一切損なわずに情報量だけが多い。動的型付けなので「返り値の型を 1 つに決める」必要もない。
  3. cond の略記としての一貫性。 (cond (a a) (t b)) の値はオペランドの値である。or をその略記と位置づけるなら、値を返すのが自然な定義になる。

結果として (or x default)(デフォルト値)、(and obj (slot obj))(ヌルガード)というイディオムが成立し、これが後の多くの言語に輸出される。


3. C 系: 型なし言語の文脈依存演算子から生まれた &&

3.1 BCPL と B には && がなく、& が文脈で意味を変えた

C の祖先は CPL → BCPL (Martin Richards, 1967) → B (Ken Thompson, 1969) である。Ritchie の論文 "The Development of the C Language" (HOPL-II, 1993) はこう述べる。

Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern.

BCPL と B は型なし言語で、すべての値は機械語 1 ワードである。当然 Boolean 型もない。そして論理演算子の意味論は次のようになっていた(同論文 14.7 "Neonatal C")。

In BCPL and B, the evaluation of expressions depends on context: within if and other conditional statements that compare an expression's value with zero, these languages place a special interpretation on the and (&) and or (|) operators. In ordinary contexts, they operate bitwise, but in the B statement if (e1 & e2) ... the compiler must evaluate e1 and if it is nonzero, evaluate e2, and if it too is nonzero, elaborate the statement dependent on the if.

つまり & は、通常の文脈ではビット AND、if の条件などの「真偽値文脈」では短絡評価の論理 AND として、同じ演算子が文脈によって意味を変えていた

3.2 && の導入 (1972年頃): Alan Snyder の提案

Ritchie は続けてこう書いている。

The short-circuit semantics of the Boolean operators in such "truth-value" context seemed desirable, but the overloading of the operators was difficult to explain and use. At the suggestion of Alan Snyder, I introduced the && and || operators to make the mechanism more explicit.

&&|| は、B の文脈依存の & | から「真偽値文脈の意味」だけを取り出して独立の演算子にしたものである。言語が命名(C)された直後、1972 年頃の "neonatal C" の時期に入った。

副産物として、C の悪名高い優先順位の問題もこのとき固定された。Ritchie は率直に失敗と認めている。

Their tardy introduction explains an infelicity of C's precedence rules. In B one writes if (a==b & c) ... ... In converting from B to C, one wants to replace & by && in such a statement; to make the conversion less painful, we decided to keep the precedence of the & operator the same relative to == ... Today, it seems that it would have been preferable to move the relative precedences of & and ==, and thereby simplify a common C idiom: to test a masked value against another value, one must write if ((a&mask) == b) ... where the inner parentheses are required but easily forgotten.

既存の B のコードを機械的に書き換えやすくするために & の優先順位を == より低いまま残した、という互換性優先の判断である。

3.3 なぜ C の && は int の 0/1 を返すのか

C には(C99 の _Bool まで)Boolean 型が存在しなかった。これは BCPL/B の「値はすべてワード」という型なしの遺産で、真偽は「0 か非 0 か」の規約で表現された。関係演算子 < == なども int の 0 か 1 を返す。&& || はこれに合わせて int の 0 か 1 を返すと定義された(現行規格でも C17 6.5.13: "the && operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it yields 0. The result has type int")。

ここで「オペランドの値を返す」という選択肢が採られなかったのは、静的型付けの制約からほぼ必然である。a && b の型はコンパイル時に 1 つに決まらなければならないが、ab の型が異なる場合(ptr && n など)、オペランドを返す仕様には union 型のような仕組みが必要になる。1972 年の C にそのような型機構はなく、「int の 0/1」が唯一現実的な答えだった。

3.4 Java: 型安全化で「Boolean を返す」が型として明文化された

C の子孫は「真偽は int」という規約の曖昧さを段階的に解消していった。C++ が bool 型を導入し(1993 年に標準化委員会が採択)、C も C99 で _Bool を追加した。Java (1995) は設計当初から独立した boolean 型を持ち、&& のオペランドと結果を boolean に限定した(JLS §15.23)。if (x = 0) のような代入と比較の取り違えが型エラーになる、という C の反省に基づく設計である。C#、Go、Swift、Kotlin も同じ方針を採っている。つまり C 系の「Boolean を返す」は、「int 0/1 を返す」(C) と「boolean 型を返す」(Java 以降) の 2 段階で成立したもので、後者は前者の型安全化である。


4. JavaScript: C の構文に Lisp の意味論を載せた合流点

JavaScript の && は C 系の見た目をしているが、意味論は Lisp 系である。これは作者の経歴から直接説明できる。

Brendan Eich は 1995 年、「ブラウザに Scheme を載せる」という誘いで Netscape に入社した。実際には経営判断で「Java に似た見た目の言語」が要求され、Eich は 1995 年 5 月の約 10 日間で最初の JavaScript (Mocha) を実装した。構文は C/Java から、第一級関数とクロージャは Scheme から、プロトタイプベースのオブジェクトは Self から採られた。

型システムの選択が返り値の仕様を決めた。JavaScript は動的型付けで、false / null / undefined / 0 / "" / NaN を偽 (falsy)、それ以外を真 (truthy) とする。これは Lisp の「NIL / 非 NIL」の一般化であり、この設計の上では「オペランドの値を返す」仕様が Lisp と同じ理由でコストなしに成立する。ECMAScript 初版 (ECMA-262, 1997 年 6 月) は §11.11 でこれを明文化している。

Note that the value produced by a && or || operator is not necessarily of type Boolean. The value produced will always be the value of one of the two operand expressions.

この仕様の上に x = x || defaultValue というデフォルト値イディオムが成立し、20 年以上使われた。ただし falsy の範囲が広い(0"" も偽)ため「0 を正当な値として渡せない」という穴があり、ES2020 で「null/undefined のときだけ右辺」を返す ?? (nullish coalescing) と ?. (optional chaining) が追加された。これは || のオペランド返し仕様が実用イディオムとして使い込まれた結果、その精密化が言語機能として要求された、という流れである。


5. 他言語への伝播と、系譜を分けている本当の要因

オペランド値を返す仕様は動的型付け言語に広く伝播した。

  • Python: x or y は「x を評価し、真ならその値、偽なら y の値」と定義される(リファレンス §6.11 に "returns the last evaluated argument" と明記)。
  • Perl: || && は最後に評価した値を返す(perlop)。$x ||= $default イディオムの基盤。
  • Ruby: 同様にオペランドを返す。x ||= y は事実上の標準イディオム。

一方、静的型付け言語は Java 型(&& は Boolean 限定・Boolean を返す)にほぼ収斂した。Rust、Go、C#、Swift、Kotlin、Haskell いずれも同じである。

この対応関係から、返り値の違いを決めている要因は「系統」そのものではなく、次の 2 点に還元できる。

  1. 静的型付けか動的型付けか。 a && b がオペランドを返すには、返り値の型が「a の型または b の型」であることを型システムが扱えなければならない。動的型付けなら問題は消滅する。静的型付けでは union 型が必要で、1970〜90 年代の主流言語には無かった。実際、union 型を持つ TypeScript は、JavaScript の a && b に対して A | B 相当の型を静的に付けており、「静的型付けだからオペランドを返せない」がもはや原理的制約ではないことを示している。
  2. 「偽」が専用型か、値の部分集合か。 Boolean 専用型の言語(ALGOL → Pascal → Java)では && の結果は Boolean 以外にありえない。「NIL / falsy」方式の言語では、真だったオペランドはそれ自体が真偽値として通用するので、正規化する理由がない。

そして歴史的事実として繰り返しておくと、この 2 条件を満たしていた Lisp ですら、1962 年の Lisp 1.5 では真偽値を返していた。オペランド値返しは「動的型付け + NIL 方式の言語では、値を捨てないほうが得だ」と誰かが気づいて初めて生まれた改良であり、それが MacLisp / Interlisp → Common Lisp / Scheme → JavaScript / Python / Perl / Ruby と継承された。C 系の「Boolean(int 0/1)返し」は、型なし言語 BCPL/B の文脈依存演算子を明示化する際に、静的型付けの制約下で唯一可能だった定義が、後に型安全化されて固定されたものである。


年表

出来事
1957–58 McCarthy、FLPL での経験から条件式(短絡評価)の必要を認識
1960 ALGOL 60: 専用の Boolean 型。 は短絡しない
1962 Lisp 1.5: and/or は短絡するが真偽値 (T/NIL) を返す述語
1966 頃〜 PDP-6 Lisp / MacLisp: and/orオペランドの値を返す仕様に
1967 / 1969 BCPL / B: 型なし。& `
1972 頃 C: Alan Snyder の提案で && `
1975 頃 Scheme: Lisp の値返し仕様を継承
1984 Common Lisp: 「or は最初の非 NIL 値、and は最後の値」を標準化
1987 頃 Perl: `
1991 Python: and/or がオペランド値を返す
1995 Java: &&boolean 型に限定。JavaScript: C 構文 + Lisp 意味論で && がオペランド値を返す
1997 ECMA-262 初版が「値は常に 2 つのオペランド式のどちらかの値」と明文化
1998 / 1999 C++98 bool / C99 _Bool
2020 ES2020: falsy の広さを補う ?? ?. を追加

参考文献(一次資料)

  • John McCarthy et al., LISP 1.5 Programmer's Manual, MIT Press, 1962. Appendix A, p.58 "Logical Connectives". PDF (Software Preservation Group)
  • Kent Pitman, The Revised Maclisp Manual, "Control Forms" (maclisp.info)
  • Dennis M. Ritchie, "The Development of the C Language", History of Programming Languages II, 1993. §14.7 "Neonatal C". PDF / ACM DL
  • ECMA-262, 1st edition (June 1997), §11.11 "Binary Logical Operators". PDF (Ecma International)
  • John McCarthy, "History of Lisp", 1978 (HOPL-I)
  • Java Language Specification §15.23 "Conditional-And Operator &&"
  • Python Language Reference §6.11 "Boolean operations"
  • Brendan Eich インタビュー (The New Stack, 2025) — "Scheme in the browser" と 10 日間の経緯
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?