関数型プログラミングとPLTサークルで「代数」という用語を何度も聞いたことがあります。特に、オブジェクト、コモナード、レンズなどについて議論する場合はそうです。この用語をグーグルで検索すると、これらの構造の数学的説明が記載されたページが表示されますが、これは私にはほとんど理解できません。誰もがプログラミングの文脈で合同代数が何を意味するか、それらの重要性は何であるか、そしてそれらがオブジェクトとコモナードとどのように関連するか説明できますか?
関数型プログラミングとPLTサークルで「代数」という用語を何度も聞いたことがあります。特に、オブジェクト、コモナード、レンズなどについて議論する場合はそうです。この用語をグーグルで検索すると、これらの構造の数学的説明が記載されたページが表示されますが、これは私にはほとんど理解できません。誰もがプログラミングの文脈で合同代数が何を意味するか、それらの重要性は何であるか、そしてそれらがオブジェクトとコモナードとどのように関連するか説明できますか?
回答:
まず、代数の考え方を理解するところから始めると思います。これは、グループ、リング、モノイドなどの代数的構造の一般化にすぎません。ほとんどの場合、これらのものはセットの観点から紹介されますが、私たちは友達同士なので、代わりにHaskellの型について話します。(ただし、ギリシャ語の文字を使用することに抵抗はありません。それらはすべてをより涼しく見せます!)
したがって、代数は、τ
いくつかの関数とIDを備えた単なる型です。これらの関数は、異なる数のタイプの引数を取りτ
、τ
:を生成します(τ, τ,…, τ) → τ
。これらはすべてのように見えます。また、「アイデンティティ」を持つこともできτ
ます。その要素は、一部の機能で特別な動作をします。
この最も単純な例はモノイドです。モノイドはτ
、関数mappend ∷ (τ, τ) → τ
とIDを持つ任意のタイプmzero ∷ τ
です。その他の例には、グループ(追加のinvert ∷ τ → τ
機能を除いてモノイドのように同じ)、リング、ラティスなどが含まれます。
すべての関数は動作しτ
ますが、異なるアリーを持つことができます。これらをとして書き出すことができます。τⁿ → τ
ここでτⁿ
、のタプルにマップしn
τ
ます。このように、IDを空のタプルτ⁰ → τ
がどこにあるのかを考えることは理にかなっています。つまり、代数の考え方を実際に単純化することができます。それは、いくつかの関数が含まれている単なる型です。τ⁰
()
代数は、コードで行うのと同じように、「除外」された数学の一般的なパターンにすぎません。人々は、前述のモノイド、グループ、ラティスなど、たくさんの興味深いことがすべて同じようなパターンに従っていることに気づき、それを抽象化しました。これを行う利点はプログラミングの場合と同じです。再利用可能な証明を作成し、特定の種類の推論を容易にします。
ただし、因数分解はまだ完了していません。これまでのところ、たくさんの関数がありますτⁿ → τ
。実際にそれらをすべて1つの関数に結合するための巧妙なトリックを実行できます。特に、モノイドを見てみましょう:とがmappend ∷ (τ, τ) → τ
ありmempty ∷ () → τ
ます。合計タイプを使用して、これらを単一の関数に変換できますEither
。次のようになります。
op ∷ Monoid τ ⇒ Either (τ, τ) () → τ
op (Left (a, b)) = mappend (a, b)
op (Right ()) = mempty
実際にこの変換を繰り返し使用して、すべての代数τⁿ → τ
について、すべての関数を1つの関数に結合できます。(実際には、我々は、任意の数の機能のためにこれを行うことができa → τ
、b → τ
等々のための任意 a, b,…
。)
これは、私たちはタイプとして代数の話をすることができますτ
し、単一のいくつかの混乱から関数Either
の単一秒τ
。モノイドの場合、この混乱は次のとおりEither (τ, τ) ()
です。(余分に持っているグループのためのτ → τ
動作)、それはです:Either (Either (τ, τ) τ) ()
。構造ごとに異なるタイプです。では、これらすべてのタイプに共通するものは何でしょうか?最も明白なことは、それらはすべて積の代数的データ型であるということです。たとえば、モノイドの場合、任意のモノイドτ で機能するモノイド引数型を作成できます。
data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
| Mempty -- here we can just leave the () out
グループ、リング、ラティス、およびその他すべての可能な構造に対して同じことを行うことができます。
これらすべてのタイプについて他に何が特別ですか?さて、それらはすべてFunctors
です!例えば:
instance Functor MonoidArgument where
fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
fmap f Mempty = Mempty
したがって、代数の考えをさらに一般化することができます。それはいくつかのfunctorのためτ
の関数f τ → τ
を持つ単なるタイプf
です。実際、これをタイプクラスとして書き出すことができます:
class Functor f ⇒ Algebra f τ where
op ∷ f τ → τ
これはファンクタによって決定されるため、「F代数」と呼ばれることがよくありF
ます。タイプクラスを部分的に適用できれば、のようなものを定義できますclass Monoid = Algebra MonoidArgument
。
さて、うまくいけば、代数とは何か、そしてそれがどのように通常の代数構造の一般化であるかを十分に理解していることでしょう。では、F代数とは何でしょうか。まあ、coはそれが代数の「双対」であることを意味します。つまり、代数を取り、いくつかの矢印を反転します。上記の定義では矢印が1つしか表示されないので、それを反転させます。
class Functor f ⇒ CoAlgebra f τ where
coop ∷ τ → f τ
そしてそれだけです!さて、この結論はちょっとばかげているように見えるかもしれません(へへ)。共代数が何であるかを教えてくれますが、それがどのように有用であるのか、なぜ私たちが気にするのかについての洞察はありません。少し例を挙げましょう。良い例が1つまたは2つ見つかった場合は、Pを取得します。
少し読んだ後、クラスとオブジェクトを表現するために合体をどのように使用するかについて、私は良い考えを持っていると思います。C
クラス内のオブジェクトの考えられるすべての内部状態を含む型があります。クラス自体はC
、オブジェクトのメソッドとプロパティを指定する合同式です。
代数の例に示されているようa → τ
にb → τ
、anyのような関数の束がある場合、合計タイプa, b,…
を使用してそれらをすべて1つの関数に結合できますEither
。デュアル「概念」タイプの機能の束を組み合わせることになるτ → a
、τ → b
というように。これは、合計タイプの2つのタイプ、つまり製品タイプを使用して行うことができます。したがって、上記の2つの関数(f
およびと呼ばれるg
)を指定すると、次のように1つの関数を作成できます。
both ∷ τ → (a, b)
both x = (f x, g x)
この型(a, a)
は簡単な方法でファンクタであるため、F代数の概念に確実に適合します。この特定のトリックにより、さまざまな関数(OOPの場合はメソッド)の束をタイプの単一の関数にパッケージ化できますτ → f τ
。
このタイプの要素は、オブジェクトの内部状態をC
表します。オブジェクトにいくつかの読み取り可能なプロパティがある場合、それらは状態に依存できる必要があります。これを行う最も明白な方法は、それらをの関数にすることです。したがって、長さプロパティ(たとえば)が必要な場合は、関数を使用します。C
object.length
C → Int
引数を取り、状態を変更できるメソッドが必要です。これを行うには、すべての引数を取り、新しいを生成する必要がありC
ます。と座標setPosition
を取るメソッドを想像してみましょう:。次のようになります。x
y
object.setPosition(1, 2)
C → ((Int, Int) → C)
ここで重要なパターンは、オブジェクトの「メソッド」と「プロパティ」が最初の引数としてオブジェクト自体を取ることです。これはself
、Python のパラメーターのようなものであり、this
他の多くの言語の暗黙のようなものです。余代数は、基本的にちょうど取るの振る舞いをカプセル化self
パラメータを:最初のものだというC
中ですC → F C
。
まとめましょう。position
プロパティ、name
プロパティ、setPosition
関数を持つクラスを想像してみましょう:
class C
private
x, y : Int
_name : String
public
name : String
position : (Int, Int)
setPosition : (Int, Int) → C
このクラスを表すには2つの部分が必要です。まず、オブジェクトの内部状態を表す必要があります。この場合、2つのと1つInt
のを保持しString
ます。(これが私たちのタイプC
です。)次に、クラスを表す合体を考え出す必要があります。
data C = Obj { x, y ∷ Int
, _name ∷ String }
書き込むプロパティは2つあります。それらはかなり簡単です:
position ∷ C → (Int, Int)
position self = (x self, y self)
name ∷ C → String
name self = _name self
次に、位置を更新できるようにする必要があります。
setPosition ∷ C → (Int, Int) → C
setPosition self (newX, newY) = self { x = newX, y = newY }
これは、明示的なself
変数を持つPythonクラスと同じです。self →
関数の束ができたので、それらを合体のための単一の関数に結合する必要があります。簡単なタプルでこれを行うことができます:
coop ∷ C → ((Int, Int), String, (Int, Int) → C)
coop self = (position self, name self, setPosition self)
タイプ((Int, Int), String, (Int, Int) → c)
— いずれの 場合もc
—はファンクターであるため、coop
希望する形式になりますFunctor f ⇒ C → f C
。
これを考えるC
と一緒にcoop
フォームIは、上記与えたクラスを指定余代数。これと同じ手法を使用して、オブジェクトに必要なメソッドとプロパティをいくつでも指定できることがわかります。
これにより、代数的推論を使用してクラスを処理できます。たとえば、クラス間の変換を表す「F代数同型」の概念を取り入れることができます。これは恐ろしい発音の用語であり、構造を維持する合同代数間の変換を意味します。これにより、クラスを他のクラスにマッピングすることを考えるのがはるかに簡単になります。
つまり、F代数は、すべてのself
オブジェクトの内部状態を含むパラメーターにすべて依存する一連のプロパティとメソッドを持つことでクラスを表します。
これまで、Haskell型としての代数と合体について説明してきました。代数はちょうどタイプでτ
機能を持つf τ → τ
と余代数だけ種類あるτ
機能を持ちますτ → f τ
。
ただし、これらのアイデアをHaskell 自体に実際に結び付けるものはありません。実際、それらは通常、タイプやHaskell関数ではなく、セットや数学関数の観点から導入されています。実際、これらの概念を任意のカテゴリに一般化できます。
いくつかのカテゴリにF代数を定義できますC
。まず、ファンクター、F : C → C
つまり内部ファンクターが必要です。(すべてのHaskell Functor
は、実際にはからの内部関数ですHask → Hask
。)次に、代数は、射A
をC
持つからの単なるオブジェクトF A → A
です。代数はを除いて同じA → F A
です。
他のカテゴリを検討することで何が得られますか?さて、私たちは異なるコンテキストで同じアイデアを使用できます。モナドのように。Haskellでは、モナドはM ∷ ★ → ★
3つの演算を持つタイプです。
map ∷ (α → β) → (M α → M β)
return ∷ α → M α
join ∷ M (M α) → M α
map
関数は、事実のほんの証拠であるM
ですFunctor
。だから我々はモナドを持つだけファンクタであると言うことができます2つの操作:return
とjoin
。
ファンクタ自体がカテゴリを形成し、ファンクタ間の射はいわゆる「自然変形」です。自然な変換は、その構造を維持しながらファンクターを別のファンクターに変換するための単なる方法です。ここにアイデアを説明するのに役立つ素晴らしい記事があります。それはリストのためconcat
だけであるについて話join
します。
Haskellファンクタでは、2つのファンクタの構成はファンクタそのものです。擬似コードでは、これを書くことができます:
instance (Functor f, Functor g) ⇒ Functor (f ∘ g) where
fmap fun x = fmap (fmap fun) x
これはjoin
、からのマッピングと考えるのに役立ちますf ∘ f → f
。タイプはjoin
です∀α. f (f α) → f α
。直感的に、すべての型に有効な関数α
がの変換としてどのように考えられるかがわかりますf
。
return
同様の変換です。タイプは∀α. α → f α
です。これは異なって見えます—最初のものα
はファンクタの「中」ではありません!幸いにも、そこに恒等関数を追加することでこれを修正できます∀α. Identity α → f α
。return
変革もそうですIdentity → f
。
今、私たちはいくつかのファンクタの周りに基づいて、ちょうど代数としてモナドについて考えることができますf
操作でf ∘ f → f
とIdentity → f
。これはおなじみに見えませんか?それはちょうど、いくつかのタイプだったモノイド、と非常によく似ていτ
操作でτ × τ → τ
と() → τ
。
したがって、モナドはモノイドのようなものですが、型を持つ代わりにファンクタを持っています。同じ種類の代数ですが、カテゴリが異なります。(これは、「モナドはエンドファンクターのカテゴリーにおける単なるモノイドである」というフレーズが私の知る限りから来ているところです。)
これで、次の2つの操作がf ∘ f → f
ありIdentity → f
ます。対応する代数を取得するには、矢印を反転するだけです。これは私たちに二つの新しい操作を与える:f → f ∘ f
とf → Identity
。上記のように型変数を追加し、とを与えること∀α. f α → f (f α)
で、それらをHaskell型に変換できます∀α. f α → α
。これは、コマンドの定義のように見えます。
class Functor f ⇒ Comonad f where
coreturn ∷ f α → α
cojoin ∷ f α → f (f α)
だから、comonadはその後である余代数 endofunctorsのカテゴリインチ
(,)
、アイデンティティファンクタはに対応し()
ます。モノイドカテゴリ内のモノイドオブジェクトは、モノイド代数に対応する矢印が付いたオブジェクトであり、モノタイプ構造として製品タイプを持つHaskのモノイドオブジェクトを表します。Cの内部関数のカテゴリのモノイドオブジェクトはCのモナドなので、そうです、あなたの理解は正しいです。:]
F代数とF代数は、帰納型(または再帰型)の推論に役立つ数学的構造です。
まず、F代数から始めます。できるだけシンプルになるように努力します。
私はあなたが再帰型とは何か知っていると思います。たとえば、これは整数のリストの型です。
data IntList = Nil | Cons (Int, IntList)
それが再帰的であることは明らかです-実際、その定義はそれ自体を参照しています。その定義は、次のタイプの2つのデータコンストラクターで構成されています。
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
単純にではなく、Nil
としてタイプを記述したことに注意してください。タイプには居住者が1人しかないため、これらは実際には理論的には同等のタイプです。() -> IntList
IntList
()
これらの関数のシグネチャをより理論的な方法で記述すると、次のようになります。
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
ここでは1
単位集合(一つの要素で設定)とされているA × B
動作は、二組のクロス積であるA
とB
(すなわち、ペアのセットのすべての要素を通過すると(a, b)
a
A
b
、すべての要素を通過しますB
)。
二組の互いに素労働組合A
とB
のセットであるA | B
セットの和集合である{(a, 1) : a in A}
と{(b, 2) : b in B}
。基本的には、A
との両方からのすべての要素のセットですB
が、この要素のそれぞれがまたはのいずれA
かB
に属するものとして「マーク」されているため、要素を選択A | B
すると、この要素がからのものA
かからのものかがすぐにわかりB
ます。
関数Nil
とCons
関数を「結合」できるので、それらはセットで機能する単一の関数を形成します1 | (Int × IntList)
。
Nil|Cons :: 1 | (Int × IntList) -> IntList
実際、Nil|Cons
関数が()
値に適用されている場合(これは明らかに1 | (Int × IntList)
セットに属しています)、関数はあたかものように動作しNil
ます。Nil|Cons
タイプの任意の値に適用される場合(Int, IntList)
(そのような値もセット内1 | (Int × IntList)
にある場合)、として動作しCons
ます。
次に、別のデータ型について考えてみましょう。
data IntTree = Leaf Int | Branch (IntTree, IntTree)
次のコンストラクタがあります。
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
これも1つの関数に結合できます。
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
このjoined
関数はどちらも似たタイプであることがわかります。どちらも次のように見えます。
f :: F T -> T
where F
は一種の変換で、私たちのタイプを取りx
、|
操作、使用法、T
そして場合によっては他のタイプで構成される、より複雑なタイプを提供します。たとえばIntList
、IntTree
F
は次のようになります。
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
どのような代数型もこの方法で記述できることがすぐにわかります。実際、それが「代数的」と呼ばれる理由です。これらは、他のタイプの「和」(和集合)と「積」(外積)で構成されています。
これで、F代数を定義できます。F代数はペア(T, f)
であり、T
はある型で、f
は型の関数ですf :: F T -> T
。この例では、F代数は(IntList, Nil|Cons)
および(IntTree, Leaf|Branch)
です。ただし、そのf
関数のタイプは各Fで同じでT
あり、f
それら自体は任意である可能性があることに注意してください。たとえば、(String, g :: 1 | (Int x String) -> String)
または(Double, h :: Int | (Double, Double) -> Double)
いくつかのためにg
とh
は、対応するFのF代数でもます。
その後、F代数準同型を導入し、次に非常に有用な特性を持つ初期F 代数を導入できます。実際、(IntList, Nil|Cons)
は初期F1代数であり(IntTree, Leaf|Branch)
、初期F2代数です。これらの用語とプロパティは、必要以上に複雑で抽象的であるため、正確な定義は示しません。
それにもかかわらず、たとえば、(IntList, Nil|Cons)
F代数であるという事実により、fold
この型に対してのような関数を定義できます。ご存知のように、foldはある種の再帰的データ型を1つの有限値に変換する一種の操作です。たとえば、整数のリストを、リスト内のすべての要素の合計である単一の値に折りたたむことができます。
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
任意の再帰的データ型でこのような操作を一般化することが可能です。
以下は、foldr
関数のシグネチャです。
foldr :: ((a -> b -> b), b) -> [a] -> b
中括弧を使用して、最初の2つの引数を最後の引数から分離していることに注意してください。これは実際のfoldr
機能ではありませんが、同型です(つまり、一方から他方を簡単に取得できます)。部分的に適用foldr
された場合、次の署名があります。
foldr ((+), 0) :: [Int] -> Int
これは整数のリストを取り、単一の整数を返す関数であることがわかります。このような関数をIntList
タイプで定義してみましょう。
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
私たちは、この関数は2つの部分から構成されていることがわかり:最初の部分は上でこの機能の動作を定義Nil
の一部IntList
、及び第二の部分は上の機能の動作を定義するCons
部分。
次に、Haskellではなく、型シグネチャで代数型を直接使用できる言語でプログラミングしているとします(厳密に言えば、HaskellではタプルとEither a b
データ型を介して代数型を使用できますが、これにより不要な冗長性が発生します)。関数を考えてみましょう:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
F代数の定義と同様に、reductor
がの関数であることがF1 Int -> Int
わかります。実際、ペア(Int, reductor)
はF1代数です。
なぜならIntList
最初のF1-代数は、種類ごとに、でありT
、各機能のためにr :: F1 T -> T
呼び出された関数が存在するが、catamorphismためr
、変換IntList
にT
、そのような機能は独特です。確かに、この例ではのカタモルフィズムはreductor
ですsumFold
。方法reductor
とsumFold
類似点に注意してください。それらはほとんど同じ構造です!でreductor
定義のs
パラメータの使用(に対応するタイプT
)相当の計算の結果の使用にsumFold xs
にsumFold
定義。
より明確にしてパターンを見やすくするために、もう1つの例を示します。ここでも、結果の折りたたみ関数から始めます。append
最初の引数を2番目の引数に追加する関数を考えます。
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
これは私たちにどのように見えるかIntList
:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
もう一度、リダクターを書き出してみましょう:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFold
は、appendReductor
に変換IntList
されるカタモフィズムですIntList
。
したがって、基本的に、F代数を使用すると、再帰的なデータ構造、つまり構造をある値に減らす操作の「フォールド」を定義できます。
F代数はF代数のいわゆる「デュアル」用語です。これによりunfolds
、再帰的なデータ型、つまりある値から再帰的な構造を構築する方法を定義できます。
次のタイプがあるとします。
data IntStream = Cons (Int, IntStream)
これは整数の無限ストリームです。その唯一のコンストラクターは次のタイプです。
Cons :: (Int, IntStream) -> IntStream
または、セットに関して
Cons :: Int × IntStream -> IntStream
Haskellを使用すると、データコンストラクターでパターンマッチングを実行できるため、IntStream
sで機能する次の関数を定義できます。
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
これらの関数をタイプの単一の関数に自然に「結合」できますIntStream -> Int × IntStream
。
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
関数の結果がIntStream
タイプの代数表現とどのように一致するかに注意してください。他の再帰的データ型についても同様のことができます。多分あなたはすでにパターンに気づいています。タイプの関数のファミリーを指します
g :: T -> F T
どこT
いくつかのタイプがあります。これから定義します
F1 T = Int × T
ここで、F代数はペア(T, g)
であり、T
はタイプでg
あり、はタイプの関数ですg :: T -> F T
。たとえば(IntStream, head&tail)
、F1代数です。繰り返しますが、F代数の場合g
とT
同様に、たとえば任意である場合(String, h :: String -> Int x String)
もありますが、これは一部のhのF1 代数でもあります。
すべてのF 代数には、いわゆる末端F代数があり、初期F代数の双対です。たとえばIntStream
、末端F代数です。つまり、すべての型T
とすべての関数に対して、に変換さp :: T -> F1 T
れるanamorphismと呼ばれる関数が存在T
しIntStream
、そのような関数は一意です。
次の関数を考えます。これは、指定された整数から始まる連続した整数のストリームを生成します。
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
今度は、機能点検しましょうnatsBuilder :: Int -> F1 Int
、です、natsBuilder :: Int -> Int × Int
:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
繰り返しますが、との間にはいくつかの類似点がnats
ありnatsBuilder
ます。これは、以前に還元剤と折りたたみで観察した接続と非常に似ています。nats
はのアナモルフィズムですnatsBuilder
。
別の例として、値と関数を取り、関数の連続するアプリケーションのストリームを値に返す関数:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
そのビルダー関数は次のとおりです。
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
次にiterate
、のアナモルフィズムですiterateBuilder
。
つまり、F代数は折りたたみ、つまり再帰構造を単一の値に減らす操作を定義することを許可し、F代数はその逆を行うことができます。単一の値から[潜在的に]無限の構造を構築します。
実際、HaskellではF代数とF代数が一致しています。これは、各タイプに「ボトム」値が存在する結果である非常に優れたプロパティです。したがって、Haskellでは、すべての再帰型に対して、折り畳みと展開の両方を作成できます。ただし、これの背後にある理論モデルは、上で示したものよりも複雑であるため、意図的に避けました。
お役に立てれば。
appendReductor
少し奇妙に見え、実際にパターンを確認するのに役立ちませんでした... :)それが正しいことを再確認できますか?の定義でr
はF1
、IntListによって決定されますか、それとも任意のFですか?
チュートリアルペーパーを読む(共)代数と(共)帰納法に関するチュートリアルは、コンピューターサイエンスにおける共代数についての洞察を与えるはずです。
以下は、あなたを説得するための引用です。
一般的に、一部のプログラミング言語のプログラムはデータを操作します。過去数十年にわたるコンピュータサイエンスの発展の中で、たとえば、プログラムが動作するデータの特定の表現に依存しないようにするために、これらのデータの抽象的な説明が望ましいことが明らかになりました。また、そのような抽象性は、正当性の証明を容易にします。
この欲求は、代数的仕様または抽象データ型理論と呼ばれる分野で、コンピュータサイエンスにおける代数的手法の使用につながりました。研究の目的は、代数からよく知られている手法の概念を使用して、それ自体がデータ型です。コンピュータサイエンティストが使用するデータタイプは、(コンストラクタ)操作の特定のコレクションから生成されることが多く、このため、代数の「初期性」がこのような重要な役割を果たします。
標準的な代数的手法は、コンピュータサイエンスで使用されるデータ構造のさまざまな本質的な側面を捉えるのに役立ちます。しかし、コンピューティングで発生する本質的に動的な構造のいくつかを代数的に記述することは難しいことがわかりました。そのような構造は通常、さまざまな方法で変換できる状態の概念を含みます。そのような状態ベースの動的システムへの正式なアプローチは、古典的な初期のリファレンスとして、一般にオートマトンまたは遷移システムを利用します。
過去10年間、そのような状態ベースのシステムは代数ではなく、いわゆる共代数として記述されるべきであるという洞察が徐々に高まりました。これらは代数の正式な双対であり、このチュートリアルで正確になります。代数の「初期性」、つまりファイナリティの二重の性質は、そのような共代数にとって重要であることが判明しました。そして、そのような最終的な共代数に必要とされる論理的な推論の原則は、帰納法ではなく共帰納法です。
プレリュード、カテゴリー理論について。 カテゴリー理論はファンクターの名前を変更する理論でなければなりません。ファンクタを定義するためにカテゴリを定義する必要があるので。(さらに、ファンクタは、自然な変換を定義するために定義しなければならないものです。)
ファンクタとは何ですか? これは、1つのセットから別のセットへの変換であり、それらの構造を維持します。(詳細については、ネット上に多くの良い説明があります)。
F代数とは何ですか? ファンクタの代数です。それはファンクタの普遍的妥当性の研究です。
コンピュータサイエンスとどのようにリンクできますか? プログラムは、情報の構造化されたセットとして表示できます。プログラムの実行は、この構造化された情報セットの変更に対応します。実行によってプログラム構造が保持されるのは良いことです。次に、実行は、この一連の情報に対するファンクタのアプリケーションと見なすことができます。(プログラムを定義するもの)。
なぜF-代数? プログラムは情報によって記述され、それに基づいて行動するため、本質的には二重です。そうすれば、主にプログラムを構成し、それらを変更させる情報は、2つの方法で表示できます。
そして、この段階で、私はそう言いたいのですが、
プログラムの存続期間中、データと状態は共存し、それらは互いに完了します。彼らは二重です。
私は明らかにプログラミングに関連するものから始め、次にいくつかの数学的なものを追加して、できるだけ具体的で現実的なものに保ちます。
http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html
帰納法は有限データに関するものであり、共帰納法は無限データに関するものです。
無限データの典型的な例は、遅延リスト(ストリーム)のタイプです。たとえば、メモリに次のオブジェクトがあるとします。
let (pi : int list) = (* some function which computes the digits of
π. *)
コンピューターはメモリの容量が限られているため、πのすべてを保持することはできません。しかし、それができることは有限のプログラムを保持することであり、それはあなたが望む任意の長いπの展開を生成します。リストの有限部分のみを使用する限り、必要なだけその無限リストで計算できます。
ただし、次のプログラムを検討してください。
let print_third_element (k : int list) = match k with
| _ :: _ :: thd :: tl -> print thd
print_third_element pi
このプログラムは、piの3桁目を印刷する必要があります。ただし、一部の言語では、関数への引数はすべて、関数に渡される前に評価されます(遅延ではなく、厳密な評価)。この縮小順序を使用すると、上記のプログラムはpiの桁を計算して永久に実行されてから、プリンター関数に渡すことができます(これは起こりません)。マシンには無限のメモリがないため、プログラムは最終的にメモリ不足でクラッシュします。これは最良の評価順序ではない可能性があります。
http://adam.chlipala.net/cpdt/html/Coinductive.html
Haskellのような遅延関数型プログラミング言語では、無限のデータ構造が至る所にあります。無限リストとよりエキゾチックなデータ型は、プログラムのパーツ間の通信に便利な抽象化を提供します。無限の遅延構造なしで同様の利便性を実現するには、多くの場合、制御フローのアクロバティックな反転が必要です。
http://www.alexandrasilva.org/#/talks.html
代数的構造は一般的に次のようになります。
これは、1。プロパティと2.メソッドを持つオブジェクトのように聞こえるはずです。あるいは、型シグネチャのように聞こえるはずです。
標準的な数学の例には、モノイド⊃グループ⊃ベクトル空間⊃「代数」が含まれます。モノイドはオートマトンのようなものです:動詞のシーケンス(例:)f.g.h.h.nothing.f.g.f
。git
常に歴史を追加し、決してそれはモノイドではなく、グループになり削除されますログ。インバースを追加すると(たとえば、負の数、分数、根、累積履歴の削除、壊れたミラーの粉砕解除)、グループが得られます。
グループには、一緒に加算または減算できるものが含まれています。たとえば、Duration
sを一緒に追加できます。(ただし、Date
sはできません。)期間は、外部の数値でスケーリングすることもできるため、(グループだけでなく)ベクトル空間に存在します。(の型シグネチャscaling :: (Number,Duration) → Duration
。)
代数⊂ベクトル空間はさらに別のことを実行できますm :: (T,T) → T
。いくつかあります。これを「乗算」と呼んでください。そうしないと、Integers
「乗算」(または「べき乗」)がどうあるべきかがはっきりしなくなります。
(これが人々が(範疇論的)普遍的性質に目を向けている理由です:乗算が何をすべきか、またはどのようにすべきかを彼らに伝えるために:
)
共乗算は、乗算よりも任意ではないと感じる方法で定義する方が簡単です。なぜならT → (T,T)
、同じ要素を繰り返すだけだからです。(「対角マップ」–スペクトル理論における対角行列/演算子のように)
Counitは通常、トレース(対角エントリの合計)ですが、重要なのはCounit が行うことです。trace
マトリックスの良い答えです。
デュアルスペースを見る理由は、一般に、そのスペースで考える方が簡単だからです。たとえば、法線ベクトルについては、法線の平面よりも考える方が簡単な場合がありますが、平面(超平面を含む)をベクトルで制御できます(これで、レイトレーサーのように、おなじみの幾何学的ベクトルについて話しています)。 。
数学者はTQFTのような楽しいものをモデリングしているかもしれませんが、プログラマーは
+ :: (Date,Duration) → Date
)、Paris
≠ (+48.8567,+2.3508)
!ポイントではなく、形状です。)、コンピュータ科学者は、代数学について話すとき、通常、デカルト積のようなセットアイッシュな操作を念頭に置いています。これが「代数はハスケルの代数である」と言ったときの意味だと思います。しかし、プログラマはのような複雑なデータ型をモデル化するために持っている程度にPlace
、Date/Time
とCustomer
可能性-Iは、双対を信じるように-そしてこれらのモデルは、現実の世界のように多くの(あるいは現実世界の少なくともエンドユーザーの視点)として見えるように、 set-worldを超えて役立つ可能性があります。