無・空・零の数理構造
―― No, None, Zero が織りなす数学的宇宙 ――
第七章 計算機科学におけるNull, Zero, False ―― デジタル世界の「無」の実装
二十世紀後半以降、計算機科学はあらゆる学問領域のなかで「無」を最も多様な形で実装し、運用してきた分野となった。コンピュータの内部世界はもともと0と1という二値の信号によって構成されているから、ゼロという概念は計算機の最も基本的な構成要素である。しかし、それとは別に、計算機科学のなかには「値がない」「値が不明である」「値が数ではない」といった、ゼロとは異なる種類の「無」が、それぞれ別個の実体として実装されている。本章では、これらデジタル世界における無の諸形態と、その帰結を検討する。
第一節 Boolean、整数、浮動小数点における0
コンピュータの最も基本的なデータ型である真偽値(Boolean)において、偽(false)はしばしば内部的に0として表現される。C言語では、整数値0が条件式のなかで偽として解釈され、それ以外の任意の値が真として解釈される。これはBoolean型と整数型のあいだの暗黙的な相互変換を許容する、緩やかな型システムの帰結である。しかし、より厳格な型システムを持つ言語、たとえばHaskellやRustでは、Booleanと整数は明確に区別され、両者のあいだの変換は明示的に行われなければならない。
整数型における0は、加法単位元としての本来のゼロを直接的に実装したものである。コンピュータ内部では、整数は二進数のビット列として表現され、すべてのビットが0であるパターンが整数値0を表す。これは加法演算において自然な単位元として機能する――どんな整数に対しても、すべてのビットが0であるパターンを加えれば(具体的にはビットごとの加算を行えば)、結果はもとの整数のままである。
浮動小数点数(IEEE 754規格)になると、ゼロの様相がやや複雑になる。この規格では、+0と-0という二つのゼロが区別されている。両者は数値としては等しいと判定されるが、内部表現としては符号ビットが異なる。+0は通常のゼロであり、-0はゼロを下から漸近する極限を示唆する値である。1/(+0)は正の無限大を返し、1/(-0)は負の無限大を返す。これは、ゼロをめぐる解析学的な微妙な振る舞いを、計算機の内部表現のなかにそのまま反映させた設計と見ることができる。
また浮動小数点数の規格には、NaN(Not a Number)という特別な値が定義されている。これは0/0、∞-∞、負数の平方根といった、数学的に「数として定義できない」演算結果を表す値である。NaNは数値ではないが、浮動小数点型のビットパターンとしては表現可能な、特異な実体である。NaNには驚くべき性質があり、いかなるNaNも、自分自身を含めて、いかなる値とも等しくないと判定される。すなわちNaN==NaNは偽である。これは、「数として比較できない」という意味論を、等号判定の振る舞いのなかに埋め込んだ設計である。NaNは、計算機科学における「No」のもっとも端的な実装といえる。
第二節 NULL ―― 値がない、という値
プログラミング言語のなかには、「値が存在しない」という事態を表す特別な定数が用意されているものが多い。C言語のNULLポインタ、JavaやC#のnullリファレンス、PythonのNone、JavaScriptのnullやundefined――いずれも、変数や参照が「指すべき対象を持たない」という状態を表す。これは、整数のゼロや浮動小数点のNaNとは異なる種類の「無」である。すなわち、「特定の型の値が一つだけある」というよりも、「値そのものが存在しない」という、メタな次元の無である。
ヌル(null)の概念を最初にプログラミング言語に導入したのは、英国の計算機科学者トニー・ホーアであった。彼は一九六五年に、ALGOL Wというプログラミング言語のなかに、参照型に対するヌル値を導入した。これによって、まだ初期化されていない参照や、対応するオブジェクトを持たない参照を、簡単に表現できるようになった。しかしホーア自身は後年、自著のなかでこの設計を「十億ドルの過ち」と呼んで悔いた。なぜなら、ヌル参照を不用意に逆参照することで生じるNullPointerException(あるいはそれに類するエラー)は、その後の数十年にわたって、世界中のソフトウェアシステムに甚大なバグと損失をもたらし続けてきたからである。
ヌル参照の問題の根源は、型システムが「ヌルである可能性」を明示的に表現しないことにある。ある変数の型が「整数」と宣言されていれば、そこには整数が入っているはずだという期待を抱く。しかし、その型がヌルを許容するならば、実際にはそこにヌルが入っているかもしれない。プログラマーがその可能性を見落とすと、ヌルを整数として扱う操作(たとえばヌルへの加算)が実行時エラーとして爆発する。これが、ヌルポインタ例外という名のもとで、現代プログラミングの最も頻出するバグとなった。
近年のプログラミング言語は、この「十億ドルの過ち」を是正するための様々な工夫を導入している。HaskellのMaybe型、RustのOption型、SwiftのOptional型、KotlinのNullable型などは、ヌルを許容するか否かを型レベルで明示的に区別する仕組みである。これらの言語では、ヌルの可能性のある値を扱うときに、その可能性をパターンマッチや明示的なチェックを介して必ず処理しなければならない。これによって、コンパイル時にヌル関連のバグの多くを発見・防止できるようになっている。
第三節 SQLとUNKNOWN ―― 三値論理の世界
リレーショナルデータベースの問い合わせ言語SQLは、ヌルの取り扱いに関して、独特の三値論理を採用している。古典的な命題論理は真と偽の二値論理であるが、SQLにおける論理は真(TRUE)、偽(FALSE)、不明(UNKNOWN)の三値からなる。データベースのテーブルのなかでヌル値を持つカラムは、その値が「不明」であることを意味し、ヌル値に対する比較演算はすべてUNKNOWNを返す。
たとえば、ある列の値が30かどうかを判定する条件「value = 30」は、その列がヌルである場合、TRUEでもFALSEでもなく、UNKNOWNを返す。「value ≠ 30」もまた、ヌルに対してはUNKNOWNを返す。すなわちSQLのなかでは、「値がヌルである行」は、value=30とvalue≠30のいずれの条件にもマッチしない。これは古典論理における排中律の違反のように見えるかもしれないが、SQLは三値論理を採用することで、ヌルが「値が不明であって、何かと比較できない」という意味論を持つことを、論理体系のなかに正確に反映している。
SQLにおいてヌルを判定するためには、特別な演算子IS NULLおよびIS NOT NULLを用いる必要がある。「value IS NULL」はvalueがヌルであるときにTRUE、そうでないときにFALSEを返し、UNKNOWNを返すことはない。これは、ヌルという「値の不在」を、テーブルの行から検出するための専用機構である。
三値論理の採用は、データベース理論において長らく論争の的であった。エドガー・コッドはリレーショナル代数の創始者の一人として、ヌル値と三値論理を支持したが、彼の弟子クリス・デイトをはじめとする一部の理論家は、ヌル値の使用を批判し、ヌル値のない厳格なリレーショナル代数を主張した。実用上は、SQLは三値論理を採用しており、世界中のデータベースシステムがこれに準拠している。
第四節 Optional型と関数型プログラミングのアプローチ
ヌル参照のもたらす問題に対する関数型プログラミングからの応答が、Optional型(あるいはMaybe型)の発明である。これは、「値が存在するかもしれないし、存在しないかもしれない」という事態を、型システムのなかで明示的に表現する仕組みである。Optional型は、Some(値)またはNone(値なし)の二つのコンストラクタを持つ代数的データ型として定義される。
Optional型の優れている点は、値の不在というno(none)を、特別なヌル参照としてではなく、通常の値として取り扱える点にある。すなわち、Noneは値の不在を表す一つの値であって、それ自体は確かに存在する。プログラマーは、Optional型の値を使用するとき、それがSomeであるかNoneであるかを必ず判定しなければならず、Noneの場合の処理を明示的に書かなければならない。これによって、ヌル関連のバグはコンパイル時に静的に検出される。
Optional型は、関数の合成を通じて、値の不在を伝播させる仕組みも備えている。Haskellのモナドや、Rustの?演算子などは、Optional型の連鎖的な処理を簡潔に記述するための構文糖衣である。これらの仕組みは、値の不在という「無」が、関数のチェーンのなかで自然な形で扱われ、最終的に明示的な処理を強制する優れた設計を実現している。
第五節 例外、エラー、そして失敗
計算機科学における「無」のもう一つの形態は、計算が失敗するという事態の表現である。例外(exception)は、通常の計算の流れから離脱して、何らかの異常状態を上位の処理に伝達する仕組みであり、Result型(成功か失敗のいずれかを表す代数的データ型)は、関数の戻り値のなかに失敗の可能性を埋め込む仕組みである。
例外とResult型の対比は、no(不可能)の取り扱いに関する二つの異なる哲学を反映している。例外は、失敗を「通常の計算の流れの外側で起きる例外的な事態」として扱う。Result型は、失敗を「計算の結果として通常起こりうる事態」として扱う。前者は処理の流れを動的に切断し、後者は処理の流れのなかに失敗を組み込む。両者はそれぞれ長所と短所を持ち、現代のプログラミング言語は両者の機構を併用して提供することが多い。
計算機科学における「無」は、こうして、ハードウェアレベルのビット表現から、データベースの三値論理、プログラミング言語の型システム、そして例外処理に至るまで、極めて多層的に実装されている。それぞれの層において、「無」が持つ意味論は微妙に異なり、それぞれの層で固有の問題と解決策が生み出されてきた。これは、ゼロや空集合や否定といった数学的な「無」の概念が、計算機という具体的な実装媒体のなかで、いかに豊かで複雑な実体として展開されうるかを示している。
第六節 ゼロ知識証明 ―― 何も明かさずに何かを示す
計算機科学の比較的新しい領域である暗号理論のなかには、「ゼロ知識証明」(zero-knowledge proof)と呼ばれる、興味深い概念がある。これは、ある命題が真であることを、その命題に関するいかなる情報をも漏らさずに、相手に納得させるための対話プロトコルである。一九八〇年代にシャフィ・ゴールドワッサー、シルビオ・ミカリ、チャールズ・ラックフォードらによって創始されたこの概念は、現代暗号理論の基礎の一つとなっている。
ゼロ知識証明の名称にある「ゼロ知識」とは、証明者が検証者に対して、命題の真理値以外には何の情報も漏らさないことを意味する。すなわち、検証者は「命題が真である」という事実を確信できるが、その命題が真である「理由」については、何も学ぶことができない。これは、情報量という観点から見ると、極めて精妙な「無」の達成である。何かを伝えながら、同時に何も伝えないという、一見すると矛盾的な事態を、暗号学的なプロトコルによって実現するのである。
ゼロ知識証明の具体的な構成は、しばしば数論的な問題(離散対数問題や因数分解問題など)の困難性に依拠する。証明者は、自分が秘密の値を知っていることを示しながら、その秘密の値そのものを開示することなく、検証者にその知識の存在を確信させる。これは、現代のブロックチェーン技術や認証システムなど、プライバシーを保護しながら情報の正当性を保証する応用において、極めて重要な役割を果たしている。
ゼロ知識という概念は、本書で扱ってきた「無」のさまざまな相のなかでも、特に異色のものである。それは、数や集合や論理的な不存在ではなく、「情報の漏洩がない」という意味での無であり、しかもその無は積極的に達成されるべき目標として設定されている。「何もない」ことを証明するために、極めて精緻な数学的構造が動員される――これは、計算機科学が「無」を扱う技術の、最も洗練された一例である。次章では、視点を物理学に転じ、現実世界における「完全な無」の不在について考察する。