代数データ型の「代数」表現は、数学のバックグラウンドを持つ人には非常に暗示的です。私の言いたいことを説明してみましょう。
基本タイプを定義した
•
_+
_X
1
_そして、_X²
_の略記_X•X
_と_2X
_の略語_X+X
_などを使用して、たとえば、リンクリスト
data List a = Nil | Cons a (List a)
↔_L = 1 + X • L
_
および二分木:
data Tree a = Nil | Branch a (Tree a) (Tree a)
↔_T = 1 + X • T²
_
さて、数学者としての私の最初の本能は、これらの式に夢中になり、L
とT
を解こうとすることです。繰り返し置換することでこれを行うことができますが、表記を恐ろしく乱用し、自由に再配置できるふりをする方がはるかに簡単に思えます。たとえば、リンクリストの場合:
_L = 1 + X • L
_
_(1 - X) • L = 1
_
L = 1 / (1 - X) = 1 + X + X² + X³ + ...
ここで、1 / (1 - X)
のべき級数展開をまったく不当な方法で使用して、興味深い結果、つまりL
型がNil
であるか、 1要素、または2要素、または3などが含まれます。
バイナリツリーに対して行うとさらに興味深いものになります。
_T = 1 + X • T²
_
_X • T² - T + 1 = 0
_
T = (1 - √(1 - 4 • X)) / (2 • X)
_T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...
_
再び、べき級数展開を使用します( Wolfram Alpha で行います)。これは、1つの要素を持つ1つのバイナリツリー、2つの要素を持つ2つのバイナリツリー(2番目の要素は左または右の枝にある)、3つの要素を持つ5つのバイナリツリーなど、(私にとって)非自明な事実を表します。
だから私の質問は-私はここで何をしていますか?これらの操作は正当化されていないように見えます(とにかく代数データ型の平方根とは何ですか?)が、理にかなった結果につながります。 2つの代数データ型の商はコンピューターサイエンスで意味を持ちますか、それとも単なる表記上のトリックですか?
そして、おそらくもっと興味深いことに、これらのアイデアを拡張することは可能ですか?たとえば、型の任意の関数を許可する型の代数の理論はありますか、または型はべき級数表現を必要としますか?関数のクラスを定義できる場合、関数の構成には意味がありますか?
免責事項:ofを説明するとき、これの多くは実際には正しく動作しません。そのため、単純にするためにそれを露骨に無視します。
いくつかの初期ポイント:
ここで「ユニオン」はおそらくA + Bの最良の用語ではないことに注意してください。具体的には a disjoint union です。なぜなら、タイプは同じです。価値のあるものについては、より一般的な用語は単に「合計タイプ」です。
シングルトンタイプは、事実上すべてのユニットタイプです。それらは代数的操作の下で同じように振る舞い、さらに重要なことに、存在する情報の量は依然として保存されます。
おそらくゼロ型も必要でしょう。標準的なものはありませんが、最も一般的な名前はVoid
です。タイプが1である値が1つあるのと同じように、タイプが0である値はありません。
ここにはまだ1つの主要な操作がありませんが、すぐに戻ります。
お気づきかもしれませんが、Haskellはカテゴリ理論から概念を借用する傾向があり、上記のすべてにそのような非常に簡単な解釈があります。
HaskのオブジェクトAとBが与えられた場合、それらの積A×Bは2つの射影を許可する一意の(同型まで)タイプですfst:A×B→Aおよびsnd:A×B→B、ここで任意のタイプCおよび関数を指定f:C→A、g:C→Bペアリングを定義できますf &&& g:C→A×B fst∘(f &&& g) = fおよびgの場合も同様です。パラメトリック性により、普遍的なプロパティが自動的に保証され、名前の私の微妙ではない選択があなたにアイデアを与えるはずです。 (&&&)
演算子は、Control.Arrow
で定義されています。
上記の双対は、inl:A→A + Bおよびinr:B→A + Bで、任意のタイプCおよび関数が与えられた場合の、連積A + Bです。 f:A→C、g:B→C、コペアリングを定義できますf ||| g:明らかな等価性が成り立つようなA + B→C。繰り返しになりますが、パラメトリックにより、トリッキーな部分のほとんどが自動的に保証されます。この場合、標準の注入は単にLeft
とRight
であり、コペアリングは関数either
です。
製品タイプと合計タイプのプロパティの多くは、上記から派生できます。シングルトン型はHaskの終端オブジェクトであり、空の型は初期オブジェクトであることに注意してください。
前述の欠落している操作に戻ると、 デカルト閉カテゴリー には、カテゴリーの矢印に対応する 指数オブジェクト があります。矢印は関数であり、オブジェクトは種類*
を持つ型であり、型A -> B
は実際にBとして動作しますA 型の代数的操作のコンテキストで。なぜこれが当てはまるかが明らかでない場合は、Bool -> A
型を検討してください。可能な入力が2つのみの場合、そのタイプの関数はA
タイプの2つの値、つまり(A, A)
に同型です。 Maybe Bool -> A
には、3つの可能な入力があります。また、代数表記法を使用するために上記のコペアリングの定義を言い換えると、恒等式Cが得られることに注意してください。A ×CB = CA + B。
whyに関しては、これはすべて理にかなっています。特に、特にべき級数展開の使用が正当化される理由は、上記の多くがタイプの「住民」を指していることに注意してください。代数的振る舞いを示すために、そのタイプを持つ値)。その視点を明示的にするには:
製品タイプ(A, B)
は、それぞれA
とB
の値を表し、独立して取得されます。そのため、固定値a :: A
には、B
の住民ごとに(A, B)
型の値が1つあります。これはもちろんデカルト積であり、製品タイプの住民数は因子の住民数の積です。
合計タイプEither A B
は、A
またはB
のいずれかの値を表し、左右の分岐が区別されます。前述のように、これは互いに素な結合であり、和タイプの住民の数は、被加数の住民の数の合計です。
指数型B -> A
は、B
型の値からA
型の値へのマッピングを表します。固定引数b :: B
には、A
の任意の値を割り当てることができます。タイプB -> A
の値は、入力ごとにそのようなマッピングを1つ選択します。これは、A
が住民を持っているのと同じ数のB
のコピーの積に相当します。
最初は型をセットとして扱うのは魅力的ですが、このコンテキストでは実際にはうまく機能しません。セットの標準的なユニオンではなく、ユニオンがばらばらになっており、交差や他の多くのセット操作の明確な解釈はありません通常、セットメンバーシップは気にしません(タイプチェッカーに任せます)。
一方、上記の構造はcounting inhabitantsについて話すのに多くの時間を費やし、enumeratingは型の可能な値はここで有用な概念です。 enumerative combinatorics にすぐにつながります。リンクされたウィキペディアの記事を参照すると、最初に行うことの1つは、「ペア」と「ユニオン」をまったく同じ意味で定義することです。 関数の生成 による積と和のタイプは、Haskellのリストとまったく同じ「シーケンス」に対して、同じテクニックを使用して同じことを行います。
Edit:ああ、ここに簡単なボーナスがあり、この点を顕著に示していると思います。コメントで、ツリータイプT = 1 + T^2
に対して、アイデンティティT^6 = 1
を導出できると述べましたが、これは明らかに間違っています。ただし、T^7 = T
does holdで、ツリーとツリーの7タプルとの間の全単射を直接構築できます。 アンドレアス・ブラスの「7本の木」 。
Edit×2:他の答えで言及された「タイプの派生」構造の主題については、 この論文の同じ著者 これは、分割の概念やその他の興味深いものを含む、さらにアイデアに基づいています。
バイナリツリーは、型の半環の式_T=1+XT^2
_によって定義されます。構成により、T=(1-sqrt(1-4X))/(2X)
は複素数の半環の同じ方程式で定義されます。したがって、同じクラスの代数構造で同じ方程式を解いていることを考えると、実際には、いくつかの類似点が見られることは驚くべきことではありません。
キャッチは、複素数の半環の多項式について推論するとき、通常、複素数が環またはフィールドを形成するという事実を使用するため、半環には適用されない減算などの演算を使用して自分自身を見つけるということです。ただし、方程式の両側からキャンセルできるルールがある場合、引数から減算を削除することができます。これは Fiore and Leinster によって証明された種類のもので、リングに関する多くの議論が半リングに転送できることを示しています。
これは、リングに関する数学的知識の多くを確実に型に変換できることを意味します。その結果、(正式なべき級数のリング内の)複素数またはべき級数を含むいくつかの引数は、完全に厳密な方法で型に引き継がれます。
ただし、ストーリーにはこれ以上のものがあります。 2つのべき級数が等しいことを示すことにより、2つの型が等しいことを証明することは1つのことです(たとえば)。ただし、べき級数の用語を調べることで、型に関する情報を推測することもできます。ここでの正式な声明がどうあるべきかはわかりません。 (私は、ブレント・ヨルギーの paper on combinatorial species が密接に関連しているが種はnotタイプと同じ。)
私が本当に気づいているのは、あなたが発見したことを微積分に拡張できるということです。微積分に関する定理は、型の半環に移すことができます。実際、有限差分についての議論でさえも転送することができ、数値解析からの古典的な定理には型理論の解釈があることがわかります。
楽しんで!
あなたがしているのは、繰り返しの関係を拡張しているようです。
L = 1 + X • L
L = 1 + X • (1 + X • (1 + X • (1 + X • ...)))
= 1 + X + X^2 + X^3 + X^4 ...
T = 1 + X • T^2
L = 1 + X • (1 + X • (1 + X • (1 + X • ...^2)^2)^2)^2
= 1 + X + 2 • X^2 + 5 • X^3 + 14 • X^4 + ...
また、型の演算のルールは算術演算のルールと同様に機能するため、代数的手段を使用して、再帰関係を展開する方法を理解することができます(明らかではないため)。
完全な答えはありませんが、これらの操作は「うまくいく」傾向があります。関連する論文は FioreとLeinsterによる複素数としてのカテゴリのオブジェクト -読んでいる間にそのことに出会いました 関連する主題に関するsigfpeのブログ ;そのブログの残りの部分は、同様のアイデアの宝庫であり、チェックする価値があります!
ちなみに、データ型を区別することもできます-これにより、データ型に適したZipperが得られます!
通信プロセスの代数(ACP)は、プロセスの同様の種類の表現を扱います。関連する中立要素を使用して、選択とシーケンスの演算子として加算と乗算を提供します。これらに基づいて、並列処理や中断など、他の構造の演算子があります。 http://en.wikipedia.org/wiki/Algebra_of_Communicating_Processes を参照してください。 「プロセス代数の簡単な歴史」という名前のオンラインペーパーもあります。
私はACPでプログラミング言語を拡張する作業をしています。昨年4月、私はScala Days 2012、 http://code.google.com/p/subscript/ で利用可能な研究論文を発表しました。
会議では、バッグの並列再帰仕様を実行するデバッガーを示しました。
バッグ= A; (バッグ&a)
ここで、Aと入力および出力アクションを表します。セミコロンとアンパサンドは、シーケンスと並列性を表します。前のリンクからアクセスできるSkillsMatterのビデオを参照してください。
より類似したバッグ仕様
L = 1 + X•L
だろう
B = 1 + X&B
ACPは、公理を使用して選択と順序の観点から並列性を定義します。ウィキペディアの記事を参照してください。バッグのアナロジーは何のためだろうか
L = 1 /(1-X)
ACPスタイルのプログラミングは、テキストパーサーとGUIコントローラーに便利です。などの仕様
searchCommand = clicked(searchButton)+ key(Enter)
cancelCommand = clicked(cancelButton)+ key(Escape)
2つの改良点を「クリック」と「キー」を暗黙的にすることにより、より簡潔に書き留めることができます(関数でScalaが許可するものなど)。したがって、次のように記述できます。
searchCommand = searchButton + Enter
cancelCommand = cancelButton + Escape
右側には、プロセスではなくデータであるオペランドが含まれるようになりました。このレベルでは、どのような暗黙の改良がこれらのオペランドをプロセスに変えるかを知る必要はありません。それらは必ずしも入力アクションに洗練されるわけではありません。出力アクションも適用されます。テストロボットの仕様。
プロセスはこの方法でデータを仲間として取得します。したがって、「アイテム代数」という用語を作り出します。
ここにもう1つ小さな追加があります。特に、 Taylor-Maclaurin アプローチを使用して導き出すことができるシリーズに焦点を当て、シリーズ展開の係数が「機能する」理由の組み合わせの洞察です。微積分。注:操作リストタイプのシリーズ展開の例は、Maclaurinシリーズです。
他の答えとコメントは代数型式(和、積、指数)の動作を扱うため、この答えはその詳細を省略し、型「計算」に焦点を当てます。
この回答では、逆コンマが重荷を持ち上げていることに気付くかもしれません。 2つの理由があります。
関数f : ℝ → ℝ
の Maclaurinシリーズ は、
f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f⁽ⁿ⁾(0)Xⁿ + ...
ここで、f⁽ⁿ⁾
は、n
のf
th導関数を意味します。
型で解釈されるMaclaurinシリーズを理解するには、型コンテキストで3つのことをどのように解釈できるかを理解する必要があります。
0
に関数を適用する(1/n!)
のような用語そして、分析からのこれらの概念は、タイプの世界で適切な対応物を持っていることがわかります。
「適切な相手」とはどういう意味ですか?それは同型のフレーバーを持っている必要があります-両方の方向で真実を保存することができれば、ある文脈で導き出せる事実を他に移すことができます。
それでは、型式の導関数はどういう意味ですか?型式とファンクターの大規模で行儀の良い(「微分可能な」)クラスには、適切な解釈をするのに十分に同様に振る舞う自然な操作があります!
パンチラインを台無しにするために、差別化に類似した操作は「ワンホールコンテキスト」を作成する操作です。 This はこの特定のポイントをさらに拡張するのに最適な場所ですが、1ホールコンテキスト(da/dx
)の基本概念は、用語(タイプx
)からの特定のタイプ(a
)の単一のサブアイテム。サブアイテムの元の場所を決定するために必要な情報を含む、他のすべての情報を保持します。たとえば、リストのワンホールコンテキストを表す1つの方法は、2つのリストを使用することです。1つは抽出されたものより前のアイテム用で、もう1つは後のアイテム用です。
この操作を差別化して識別する動機は、次の観察から得られます。タイプa
のホールを持つタイプx
のワンホールコンテキストのタイプを意味するda/dx
を記述します。
d1/dx = 0
dx/dx = 1
d(a + b)/dx = da/dx + db/dx
d(a × b)/dx = a × db/dx + b × da/dx
d(g(f(x))/dx = d(g(y))/dy[f(x)/a] × df(x)/dx
ここで、1
と0
は、それぞれ1人と0人の住民がいるタイプを表し、+
と×
は通常どおり合計と製品タイプを表します。 f
およびg
は、型関数または型式フォーマーを表すために使用され、[f(x)/a]
は、前の式のa
ごとにf(x)
を代入する操作を意味します。
これは、ポイントフリースタイルで記述され、f'
と記述して、関数f
の派生関数を意味します。したがって、
(x ↦ 1)' = x ↦ 0
(x ↦ x)' = x ↦ 1
(f + g)' = f' + g'
(f × g)' = f × g' + g × f'
(g ∘ f)' = (g' ∘ f) × f'
これが望ましいかもしれません。
注意:型とファンクターの同型クラスを使用して導関数を定義すると、等式を厳密かつ厳密にすることができます。
今、特に、加算、乗算、および合成の代数演算に関する計算の規則(多くの場合、Sum、Product、Chain規則と呼ばれます)が、 '穴を開ける」。さらに、定数式またはtermx
に「穴を開ける」という基本的なケースも微分として動作するため、誘導により、すべての代数型式に対して微分のような動作が得られます。
これで、微分を解釈できます。型式のn
th 'derivative'、dⁿe/dxⁿ
はどういう意味ですか?これはn
- placeコンテキストを表すタイプです。タイプn
のx
用語で「満たされた」ときにe
を生成する用語です。後で来る '(1/n!)
'に関連するもう1つの重要な観察結果があります。
型の世界ではすでに0
の解釈があります:メンバーのない空の型です。組み合わせの観点から、型関数を適用することはどういう意味ですか?より具体的には、f
が型関数であると仮定すると、f(0)
はどのように見えますか?確かに、タイプ0
にはアクセスできないため、x
を必要とするf(x)
の構造は使用できません。残っているのは、不在のときにアクセスできる用語であり、タイプの「不変」または「定数」部分と呼ぶことができます。
明示的な例として、Maybe
ファンクターを使用します。これは、代数的にx ↦ 1 + x
として表すことができます。これを0
に適用すると、1 + 0
を取得します。これは1
と同じです。可能な値はNone
の値のみです。リストについても同様に、空のリストに対応する用語のみを取得します。
それを元に戻し、タイプf(0)
を数値として解釈するとき、タイプf(x)
の用語の数のcountと考えることができます(すべてのx
)は、x
にアクセスせずに取得できます。つまり、「空のような」用語の数です。
(1/n!)
を型として適切に直接解釈することは考えられません。
ただし、上記を考慮してタイプf⁽ⁿ⁾(0)
を考慮すると、タイプf(x)
の用語のn
- placeコンテキストのタイプとして解釈できることがわかります。これは、タイプx
をまだ含んでいない-つまり、それらをn
回「統合」すると、結果の用語はn
x
s、それ以上、それ以下。次に、タイプf⁽ⁿ⁾(0)
の数値としての解釈(f
のMaclaurinシリーズの係数のように)は、そのような空のn
- placeコンテキストがいくつあるかの単純なカウントです。もうすぐです!
しかし、(1/n!)
はどこで終わるのでしょうか?タイプ「分化」のプロセスを調べると、複数回適用された場合、サブタームが抽出される「順序」が保持されることがわかります。たとえば、タイプ(x₀, x₁)
のx × x
という用語と、その中に「穴を開ける」操作を2回考えます。両方のシーケンスを取得します
(x₀, x₁) ↝ (_₀, x₁) ↝ (_₀, _₁)
(x₀, x₁) ↝ (x₀, _₀) ↝ (_₁, _₀)
(where _ represents a 'hole')
両方とも同じ用語に由来しますが、2つの要素から2つの要素を取得し、順序を維持する2! = 2
の方法があるためです。一般的に、 n!
n
からn
要素を取得する方法があります。したがって、n
要素を持つファンクタータイプの構成の数を取得するには、タイプf⁽ⁿ⁾(0)
をカウントし、n!
で除算する必要があります Maclaurinシリーズの係数のように、正確に。
したがって、n!
で割ると、単にそれ自体として解釈可能になります。
まず、いくつかの観察:
連鎖規則があるので、型導関数を同型クラスとして形式化する場合、 暗黙の微分 を使用できます。しかし、暗黙の微分では、減算や除算のようなエイリアンの操作は必要ありません!したがって、再帰型定義を分析するために使用できます。リストの例を挙げると、
L(X) ≅ 1 + X × L(X)
L'(X) = X × L'(X) + L(X)
そして、我々は評価することができます
L'(0) = L(0) = 1
maclaurinシリーズのX¹
の係数を取得します。
しかし、これらの式は実際には厳密に「微分可能」であると確信しているため、暗黙的にのみ、および導関数が確かに一意である関数ℝ→withとの対応があるため、「を使用して値を取得しても違法」操作、結果は有効です。
さて、同様に、2番目の観測を使用するために、関数?→withとの対応(準同型か?)により、関数hasMaclaurinシリーズ、すべてのシリーズを見つけることができる場合、上記の原則を適用して厳密にすることができます。
関数の構成に関する質問については、チェーンルールが部分的な答えを提供していると思います。
これが何個のHaskellスタイルのADTに適用されるかは定かではありませんが、全部ではないとしても多くあると思います。私はこの事実の本当に素晴らしい証拠を発見しましたが、このマージンはそれを抑えるには小さすぎます...
さて、確かにこれはここで起こっていることを解決する唯一の方法であり、おそらく他にも多くの方法があります。