関数型プログラミングと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表します。オブジェクトにいくつかの読み取り可能なプロパティがある場合、それらは状態に依存できる必要があります。これを行う最も明白な方法は、それらをの関数にすることです。したがって、長さプロパティ(たとえば)が必要な場合は、関数を使用します。Cobject.lengthC → Int
引数を取り、状態を変更できるメソッドが必要です。これを行うには、すべての引数を取り、新しいを生成する必要がありCます。と座標setPositionを取るメソッドを想像してみましょう:。次のようになります。xyobject.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人しかないため、これらは実際には理論的には同等のタイプです。() -> IntListIntList()
これらの関数のシグネチャをより理論的な方法で記述すると、次のようになります。
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
ここでは1単位集合(一つの要素で設定)とされているA × B動作は、二組のクロス積であるAとB(すなわち、ペアのセットのすべての要素を通過すると(a, b)aAb、すべての要素を通過します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を使用すると、データコンストラクターでパターンマッチングを実行できるため、IntStreamsで機能する次の関数を定義できます。
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常に歴史を追加し、決してそれはモノイドではなく、グループになり削除されますログ。インバースを追加すると(たとえば、負の数、分数、根、累積履歴の削除、壊れたミラーの粉砕解除)、グループが得られます。
グループには、一緒に加算または減算できるものが含まれています。たとえば、Durationsを一緒に追加できます。(ただし、Datesはできません。)期間は、外部の数値でスケーリングすることもできるため、(グループだけでなく)ベクトル空間に存在します。(の型シグネチャ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を超えて役立つ可能性があります。