Common Lisp のハッシュテーブルと EQUALP の複雑性
Common Lisp のハッシュテーブルは、標準で EQ、EQL、EQUAL、そして EQUALP の4つのテスト述語をサポートしています。この中で、特に EQUALP が興味深いのは、異なる内部表現を持つ数値でも、その数学的な値が等しければ EQUALP では同じとみなされる点です。例えば、1 と 1.0、あるいは 1/2 と 0.5 は EQUALP で T を返します(Lobsters)。
EQUALPで等しいと判定されるオブジェクトは、必ず同じハッシュ値を生成しなければならない、という一貫性が求められます。
ハッシュ関数はこの一貫性を保つ必要があります。つまり、EQUALP が T を返す2つのオブジェクトに対して、ハッシュ関数は同じハッシュ値を返す必要があります。しかし、逆は必ずしも真ではありません。
同じハッシュ値を持つからといって、EQUALP で T を返すとは限りません。この特性は、ハッシュテーブルの性能と正確性を確保する上で非常に重要です(Lobsters)。
EQUALP に適合しないハッシュ関数の例
記事では、EQUALP に適合するハッシュ関数と、適合しないハッシュ関数の例を挙げています。例えば、以下の不適合なハッシュ関数は、1 と 1.0 が EQUALP で等しいにもかかわらず、異なるハッシュ値を返してしまいます。
| オブジェクトの種類 | ハッシュ値の例 |
|---|---|
| rational (整数、分数) | 1337 |
| float (浮動小数点数) | 7117 |
| complex (虚数部が0の場合) | realpart のハッシュ値 |
| その他 | 15 |
この例では、1 は rational、1.0 は float と判断され、それぞれ 1337 と 7117 という異なるハッシュ値が返されます。これでは EQUALP の仕様を満たしません。適切なハッシュ関数は、単に一貫性を保つだけでなく、EQUALP で等しくないオブジェクトには異なるハッシュ値を返すことで、ハッシュテーブルの衝突を減らし、パフォーマンスを向上させる必要があります(Lobsters)。
Common Lisp の「数値タワー」と数学的等価性
Common Lisp には「数値タワー」と呼ばれる包括的な数値型システムが存在します。これは整数、分数、浮動小数点数、複素数など、さまざまな数値表現をサポートするものです。
このシステムの素晴らしい点は、数値がその基になる表現にかかわらず、数学的に同じ値であれば # で等しいと判断されることです。例えば、1 と 1.0 は、内部表現が異なっても数学的には同じ値です(Lobsters)。
この数学的等価性の概念が、
EQUALP述語の下で高品質なハッシュ関数を構築する際の最大の課題となります。
異なる表現を持つが数学的に等しい数値に対して、同じハッシュ値を生成しつつ、かつ衝突が少なく均等に分布するハッシュ値を生成するのは非常に難しい問題です。記事では特に、ECL(Embeddable Common Lisp)で使われている二進浮動小数点数の表現を前提として、この問題に取り組んでいます。数値タワーの柔軟性が、ハッシュの実装に新たな複雑性をもたらしていると言えるでしょう。
エンジニア目線の考察:数値型の一貫性は AI 開発でも重要
今回の EQUALP に関するハッシュ関数の話題は、一見すると Common Lisp のニッチな領域に見えるかもしれません。しかし、数値型の厳密な比較と一貫したハッシュ処理は、現代のソフトウェア開発、特にデータ処理やAI/LLMの分野でも非常に重要な意味を持つと感じます。
例えば、Python の decimal 型や numpy の浮動小数点数演算で、異なる精度や表現の数値が「等しい」と判定されるか、そしてそれらをキーとしてハッシュマップに入れる際にどのような挙動を示すかは、データの一貫性や処理速度に直結します。GCP の BigQuery で数値データを扱う際も、FLOAT64 と NUMERIC では振る舞いが異なるため、どの型を選ぶかは計算結果の精度や比較処理のロバストさに大きく影響します。
今回のように、特定の条件下で「等しい」とみなされる異なる表現の数値に対して、いかに安定したハッシュ値を生成するかは、言語やプラットフォームを問わず重要な課題です。Python の __hash__ メソッドや TypeScript の Map オブジェクトのキーの挙動を設計する際にも、同じような考慮が必要となるのではないでしょうか。AI の学習データの前処理で特徴量ハッシュを行う場合など、意図しない衝突や不均一な分布はモデルの性能低下に直結しかねません。
既存のシステムに数値型を扱う機能を組み込む場合、今回の Common Lisp の議論は、より広範な言語やフレームワークで数値の比較とハッシュがどのように実装されているかを再確認する良いきっかけになると考えます。特に大規模なデータ処理を行う際には、こうした基盤技術の詳細がシステムの安定性やパフォーマンスを大きく左右する可能性があり、注意深く検証する必要があるでしょう。