core 設計
設計目標
luna-generic は LunaFlow 全体に共有される代数語彙を与え、 arithmetic、luna-complex、linear-algebra、luna-poly などが同じ能力境界を再利用できるようにします。
主な設計判断
- trait グラフを層化し、小さく保つ。
Ring、Field、Integral、Natのような構造 traits と、Zero、One、Inverse、Conjugateのような操作 traits を分離する。- 変換は、
BigIntで表される ℤ で出会う二つの半分に分かれます。対象側は標準写像 ℤ ->R(FromInteger)で、一意であり常に準同型です。ソース側はIntegral::normalizeで、代表元を選びますが、準同型になるのは ℤ そのものの場合だけです。lift_toは両者を合成し、演算については何も約束しません。 - どちらの半分も単一パラメータの trait です。すべての変換は始対象である環 ℤ を経由して分解されるため、二つの型を関係づける trait は必要ありません。
Integralは法則from_integer(normalize(x)) == xによってFromIntegerを拡張し、整数型を代表元の選ばれた ℤ の商にします。FromIntegerは総称的な整数ソースではなくBigIntを受け取るので、trait 同士が互いを参照することはありません。- 符号なし型に加法逆元を主張させず、数学的整合性を保つ。
Fieldは可換体を意味します。その法則は、ほかの構造 trait と同じく実装者への契約であり、コンパイラが検査するものではありません。
数学的背景
各構造 trait は代数的構造のシグネチャで、その構造の公理がインスタンスの約束する法則です。 はキャリア全体を動きます:
| Trait | 構造 | スーパー trait に追加される法則 |
|---|---|---|
AddMonoid | モノイド | , |
MulMonoid | モノイド | , |
AddGroup | 群 | |
MulGroup | 群 | |
Semiring | 半環 | , , , |
Ring | 環 | AddGroup の法則 |
Field | 体 | 、 なら 、 |
スーパー trait のグラフは構造間の包含を反映しています。すべての環は半環であり、すべての半環は加法モノイドでも乗法モノイドでもある、という具合です。T : Ring で制約された関数はちょうど環の公理の帰結を使えるので、ジェネリックなコードはすべてのインスタンスで正しくなります。
このような構造の間の準同型 はシグネチャのすべての演算を保存します:
ℤ の商としての整数
この節では FromNat、FromInteger、Integral、lift_to の背後にある数学を説明します。数学抜きでの使い方はcore チュートリアルにあります。
標準写像は一意である
任意の環 R に対して、環準同型 ℤ -> R はちょうど一つ存在します。環準同型 φ は 1 を 1 に送らなければならず、加法性から n > 0 では φ(n) = 1 + ... + 1(n 個)、さらに φ(-n) = -φ(n) が強制されます。逆に、分配法則により n ↦ n·1 は + と * を保存します。同じ議論により、任意の半環 R に対して半環準同型 ℕ -> R もちょうど一つ存在します。圏論の言葉では、ℕ と ℤ は始対象です。
完全な導出を、まず ℕ について示します。 を半環とし、 を再帰的に定義します:
加法性。 に関する帰納法( における の結合法則を使用):
乗法性。 に関する帰納法(吸収則 、加法性、分配法則を使用):
一意性: 任意の半環準同型 は と を満たし、これは同じ漸化式なので、帰納法により です。
ℤ については、 を環とします。すべての整数は自然数の差 として書けるので、 と置きます。これは well-defined です。 は ℕ で を意味し、したがって で となり、両辺に を加えると( は可換) を得ます。項ごとに加法的で、分配法則により乗法的です:
環準同型は も満たさなければならないので、負の数でも と一致し、 は一意です。11 圏論の言葉では、ℕ は半環の圏の始対象、ℤ は環の圏の始対象です。ℤ は加法モノイド ℕ のグロタンディーク群であり、上で使ったのはこの構成です。
この写像は R だけで決まるので、対象側の性質として単一パラメータの trait で表せます: FromNat::from_natural と FromInteger::from_integer。
固定幅の整数
Int は 2^32 を法として加算・乗算するので、環としては ℤ/2^32 です。その from_integer は全射である簡約 ℤ -> ℤ/2^32 です。normalize は逆向きで、各剰余類から整数を一つ、[-2^31, 2^31) にあるものを選びます。法則 from_integer(normalize(x)) == x は、normalize が簡約の切断であることをちょうど表しています:
normalizeは左逆を持つので単射です。- その像は各類のちょうど一つの元を含みます。単射なので高々一つ、法則により少なくとも一つです。
- 準同型ではありません:
normalize(2147483647 + 1) = -2^31ですが、normalize(2147483647) + normalize(1) = 2^31です。
記号で書くと、、 を核が の簡約写像として、同梱のインスタンスは次を使います
ここで は の値をとります。 と が同じ値を与えるのでどちらも well-defined で、それぞれ との差が の倍数なのでどちらも を満たします。上の単射性の二つの段階を書き下すと次のとおりです:
加法性のずれは法の倍数です:
Int で 、 とするとずれは なので、どのように代表元を選んでも修正できません。 が準同型になるのは のとき、つまり BigInt の場合だけです。
切断が何を保存するかはhom 設計で説明します。
変換が準同型になるとき
lift_to : S -> R は R::from_integer と S::normalize の合成です。S が ℤ/m(BigInt では m = 0)のとき、環準同型 ℤ/m -> R が存在するのは R で m·1 = 0 となるとき、かつそのときに限ります:
ψがそのような準同型なら、Rにおいて0 = ψ(0) = ψ(m·1) = m·1です。m·1 = 0なら、標準写像ℤ -> Rはmℤを0に送るので ℤ/m を経由して分解します。ℤ -> ℤ/mは全射なので、この分解は一意です。
このとき lift_to はこの準同型そのものです。標準写像 ℤ -> R を π : ℤ -> ℤ/m を用いて ψ ∘ π と書くと、lift_to = ψ ∘ π ∘ normalize = ψ です。したがって:
Int64 -> Intは準同型です。2^64 ≡ 0 (mod 2^32)だからです。Int -> Int64は準同型ではありません。2^32 ≢ 0 (mod 2^64)だからです。- 固定幅の整数から
BigInt、Float、Doubleへの準同型は存在しません。これらではすべてのm > 0についてm·1 ≠ 0だからです。
同じ議論を一つの連鎖で書きます。 を標準写像とし、 のとき です:
二つの法の例では、ℤ/2^32 で ですが、ℤ/2^64 では なので です。
符号なし型が Semiring で止まる理由
UInt、UInt16、UInt64 は回り込む自然数として読まれます。Nat は非負の代表元を約束し、それらを使うコードは値を個数やサイズとして扱います。ℕ には加法逆元がないので、環ではなく半環です:
これは矛盾です。抽象的な環としては符号なし型は ℤ/2^k で、負元 を持ちます。それを公開すると、環向けに書かれたジェネリックなコードで -1 が黙って を意味することになり、Nat という読み方はまさにこの混乱を避けています。また標準ライブラリは符号なし型に Neg を提供しておらず、MoonBit ではこのパッケージが外部の型に外部の trait のインスタンスを追加できないため、ラッパー型なしにはそもそも AddGroup を実装できません。標準写像は全域のままです。UInt での from_integer(-1) は 、つまり ℤ/2^32 における の像です。
trait が互いを参照しない理由
すべての変換は ℤ を経由して分解されます: S -> ℤ -> R。前半は S(Integral)だけに、後半は R(FromInteger)だけに依存するので、二つの型を関係づける trait は必要ありません。FromInteger は総称的な整数ソースではなく BigInt を受け取るため、Integral は循環なしにそれを拡張できます。
体と斜体
Field は乗法が可換な Ring + Inverse + Div です。可換性を述べるメソッドはありませんが、契約の一部です。
定義
斜体(可除環)とは、 で、すべての が両側逆元 を持つ環です。体 とは乗法が可換、 である斜体です。両者の違いはこの法則だけで、Ring + Inverse + Div のメソッドシグネチャでは区別できません。
体でない斜体の標準的な例は、基底 と を持つハミルトンの四元数 ℍ です。 の右から を掛けると となるので 、同様に です:
可換性がないと何が壊れるか
どの斜体でも、積の逆元は順序が逆になります:
逆の順序はもう一方の積の逆元 であり、逆元をとる操作は単射なので
ℍ では ですが、 です。除算も同じように曖昧です。 と は一般に異なる元で、Div はそのうち一つしか提供しません。
F : Field で制約されたジェネリックなコードは に依存してかまいません。 を に書き換える、 を として計算する、計算を減らすために積を並べ替える、などです。Field を実装した非可換な型はコンパイルを通り、そのようなコードから誤った答えを受け取ります。これが trait が可換性を述べる理由であり、体でない斜体がこれを実装してはならない理由です。斜体でも動くジェネリックなコードは Ring + Inverse + Div を要求し、因子の順序を保ちます。
有限の斜体はすべて可換なので、22 ウェダーバーンの小定理(1905): 有限の斜体は体である。実数上では、フロベニウスの定理(1877)により、有限次元の結合的な可除代数は ℝ、ℂ、ℍ だけです。 この区別が問題になるのは無限の型だけです。
浮動小数点のインスタンス
Float と Double は丸めの範囲で Field を実装します。乗法は厳密に可換で です。IEEE 754 は正確な積を丸め、正確な積は順序に依存しないからです。結合法則と分配法則は近似的にしか成り立たず、0 には逆元がありません。inv はそこで中断します。
採用しなかった代替案
- 二引数の変換 trait
Into[S, R]: MoonBit の trait にはSelfしかなく、ℤ を経由する分解があるので不要です。 - 広い「数」trait: ℤ、ℤ/2^k、近似的な実数、体の違いを隠してしまいますが、それこそジェネリックなコードが尊重すべき違いです。
NatHomomorphismとIntegralHomomorphismを変換インターフェースにすること: 固定幅のソースには与えられない準同型を約束していました。非推奨の trait としてのみ残っています。- 斜体専用の trait: 同梱の型には不要で、可換性なしで動く必要のあるコードは
Ring + Inverse + Divを要求できます。
境界
- このパッケージは行列、複素数、多項式、解析関数、数値アルゴリズムを定義しません。
- 正確系と近似系の意味論差を無理に隠しません。
- コンパイル時に法則を検査しません。
Fieldの可換性を含め、すべての構造 trait の法則は実装者への契約で、hom API の道具でテストします。 - 任意精度の ℕ はモデル化しません。そのような型は ℤ の商ではないので
Integralになれません。