web-dev-qa-db-ja.com

プログラミングの文脈で「代数」とはどういう意味ですか?

特にオブジェクト、コモナ、レンズなどについての議論の場合、関数型プログラミングやPLTサークルで「代数」という用語を何度か聞いたことがあります。この用語をグーグルで検索すると、これらの構造の数学的な説明を提供するページが提供されますが、これは私にはほとんど理解できません。誰もがプログラミングの文脈での代数が何を意味するのか、それらの重要性は何なのか、それらはオブジェクトやコマンドにどのように関係するのかを説明できますか?

330
missingfaktor

代数

始める場所は、代数の概念を理解することだと思います。これは、グループ、リング、モノイドなどの代数構造の一般化です。ほとんどの場合、これらのことはセットの観点から紹介されますが、私たちは友人であるため、代わりにHaskellのタイプについて説明します。 (ただし、ギリシャ文字を使用することは避けられません。すべての文字がクールに見えます!)

したがって、代数は、いくつかの関数とアイデンティティーを持つτ型にすぎません。これらの関数は、τ型の異なる数の引数を取り、τを生成します。カリー化されていないため、すべて(τ, τ,…, τ) → τのように見えます。また、いくつかの関数で特別な動作をするτの要素である「アイデンティティ」を持つこともできます。

これの最も簡単な例はモノイドです。モノイドは、任意のタイプτであり、関数mappend ∷ (τ, τ) → τとID mzero ∷ τを持ちます。他の例には、グループ(余分なinvert ∷ τ → τ関数を除いてモノイドに似ています)、リング、ラティスなどが含まれます。

すべての関数はτで動作しますが、異なるアリティを持つことができます。これらをτⁿ → τとして書き出すことができます。ここで、τⁿnτのタプルにマップされます。このように、アイデンティティをτ⁰ → τとして考えるのは理にかなっています。ここで、τ⁰は単なる空のTuple ()です。したがって、代数の概念を実際に単純化することができます。それは、いくつかの関数を備えた単なる型です。

代数は、コードで行うように、「ファクタリング」された数学の一般的なパターンです。人々は、前述のモノイド、グループ、ラティスなどの興味深いものがすべて同じようなパターンに従っていることに気づいたので、それを抽象化しました。これを行う利点は、プログラミングの場合と同じです。再利用可能な証明を作成し、特定の種類の推論を容易にします。

F代数

ただし、ファクタリングはまだ完了していません。これまでのところ、たくさんの関数τⁿ → τがあります。これらをすべて1つの関数に結合するために、実際に巧妙なトリックを行うことができます。特に、モノイドを見てみましょう。mappend ∷ (τ, τ) → τmempty ∷ () → τがあります。合計タイプEitherを使用して、これらを単一の関数に変換できます。次のようになります。

op ∷ Monoid τ ⇒ Either (τ, τ) () → τ
op (Left (a, b)) = mappend (a, b)
op (Right ())    = mempty

実際にこの変換を繰り返し使用して、allτⁿ → τ関数をany代数の単一の関数に結合できます。 (実際、これはanya → τに対して任意の数の関数b → τa, b,…などに対して行うことができます。)

これにより、Eithersのいくつかの混乱から単一のτへのsingle関数を持つタイプτとして代数について話すことができます。モノイドの場合、この混乱はEither (τ, τ) ();です。グループ(余分なτ → τ操作がある)の場合、Either (Either (τ, τ) τ) ()です。構造ごとに異なるタイプです。では、これらすべてのタイプに共通するものは何ですか?最も明白なことは、それらはすべて積の単なる和、つまり代数データ型であるということです。たとえば、モノイドの場合、any monoidτで機能するモノイド引数型を作成できます。

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

したがって、代数のアイデアをさらに一般化できます。それは、いくつかのタイプτと関数f τ → τで、ファンクターfです。実際、これをタイプクラスとして書き出すことができます。

class Functor f ⇒ Algebra f τ where
  op ∷ f τ → τ

これは、ファンクターFによって決定されるため、しばしば「F代数」と呼ばれます。型クラスを部分的に適用できれば、class Monoid = Algebra MonoidArgumentのようなものを定義できます。

炭代

さて、うまくいけば、代数が何であるか、そしてそれがどのように通常の代数構造の一般化であるかをよく理解できたと思います。それでは、F代数とは何ですか?まあ、共同はそれが代数の「双対」であることを意味します-つまり、代数を取り、いくつかの矢印を反転します。上記の定義には矢印が1つしか表示されていないため、次のように反転します。

class Functor f ⇒ CoAlgebra f τ where
  coop ∷ τ → f τ

そして、それだけです!さて、この結論は少し気味が悪いように見えるかもしれません(へ)。それはwhatが代数であるということを伝えますが、それがどのように有用であるか、なぜ私たちが気にするのかについての洞察を実際には与えません。良い例が1つまたは2つ見つかったら、またはその2つを思いついたら、少し後で説明します。

クラスとオブジェクト

少し読んだ後、クラスとオブジェクトを表現するために、代数を使用する方法について良いアイデアを持っていると思います。クラス内のオブジェクトの考えられる内部状態をすべて含むC型があります。クラス自体は、オブジェクトのメソッドとプロパティを指定するCの上の代数です。

代数の例に示されているように、a → τに対してb → τa, b,…のような関数の束がある場合、Eitherを使用してすべてを1つの関数に結合できます。合計タイプ。二重の「概念」は、タイプτ → aτ → bなどの一連の関数を組み合わせたものです。これを行うには、双対の合計タイプ(製品タイプ)を使用します。したがって、上記の2つの関数(fおよびgと呼ばれる)が与えられた場合、次のような単一の関数を作成できます。

both ∷ τ → (a, b)
both x = (f x, g x)

タイプ(a, a)は単純な方法でファンクターであるため、F代数の概念に確実に適合します。この特定のトリックにより、さまざまな関数(またはOOPの場合はメソッド)の束をτ → f τ型の単一の関数にパッケージ化できます。

タイプCの要素は、オブジェクトの内部状態を表します。オブジェクトに読み取り可能なプロパティがある場合、状態に依存できる必要があります。これを行う最も明白な方法は、それらをCの関数にすることです。したがって、長さプロパティ(たとえば、object.length)が必要な場合、関数C → Intがあります。

引数を取り、状態を変更できるメソッドが必要です。これを行うには、すべての引数を取り、新しいCを生成する必要があります。 setPositionxの座標を取るyメソッドを想像してみましょう:object.setPosition(1, 2)C → ((Int, Int) → C)のようになります。

ここで重要なパターンは、オブジェクトの「メソッド」と「プロパティ」がオブジェクト自体を最初の引数として使用することです。これは、Pythonのselfパラメーターと、他の多くの言語の暗黙的なthisと同じです。コアジェブラは、基本的にselfパラメーターを取得する動作をカプセル化するだけです。これは、C → F Cの最初のCです。

それでは、すべてをまとめましょう。 positionプロパティ、nameプロパティ、およびsetPosition関数を持つクラスを想像してみましょう。

class C
  private
    x, y  : Int
    _name : String
  public
    name        : String
    position    : (Int, Int)
    setPosition : (Int, Int) → C

このクラスを表すには2つの部分が必要です。まず、オブジェクトの内部状態を表す必要があります。この場合、2つのIntsと1つの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)——anyc—はファンクターであるため、coopにはFunctor f ⇒ C → f Cという形式があります。

これを考えると、Ccoopと一緒に、上で与えたクラスを指定する合算を形成します。この同じ手法を使用して、オブジェクトに必要なメソッドとプロパティをいくつでも指定する方法を確認できます。

これにより、代数的推論を使用してクラスを処理できます。たとえば、クラス間の変換を表す「F代数準同型」の概念を取り入れることができます。これは恐ろしく聞こえる用語で、構造を保存する炭代間の変換を意味します。これにより、クラスを他のクラスにマッピングすることを非常に簡単に考えることができます。

要するに、F代数は、すべてのオブジェクトの内部状態を含むselfパラメーターにすべて依存する一連のプロパティとメソッドを持つことにより、クラスを表します。

その他のカテゴリー

これまで、Haskell型としての代数と代数について説明してきました。代数は関数τを持つf τ → τ型だけであり、代数は関数τを持つτ → f τ型だけです。

しかし、これらのアイデアをHaskellと実際に結び付けるものはありませんper se。実際、それらは通常、タイプとHaskell関数ではなく、セットと数学関数の観点から導入されています。実際、これらの概念をanyカテゴリーに一般化できます!

いくつかのカテゴリCに対してF代数を定義できます。まず、ファンクターF : C → C、つまりendofunctorが必要です。 (すべてのHaskell Functorsは、実際にはHask → Haskからの内在関数です。)それから、代数は、オブジェクトAからCで、射影F A → Aを持ちます。代数はA → F Aを除いて同じです。

他のカテゴリを考慮することで何が得られますか?まあ、異なるコンテキストで同じアイデアを使用できます。モナドのように。 Haskellでは、モナドは3つの操作を持つM ∷ ★ → ★型です:

map      ∷ (α → β) → (M α → M β)
return   ∷ α → M α
join     ∷ M (M α) → M α

map関数は、MFunctorであるという事実の単なる証明です。そのため、モナドは、two操作を伴う単なるファンクターであると言えます:returnおよびjoin

ファンクター自体がカテゴリーを形成し、それらの間の射はいわゆる「自然変換」です。自然な変換は、その構造を維持しながら1つのファンクターを別のファンクターに変換する方法にすぎません。 こちら アイデアの説明に役立つ素敵な記事。 concatについて説明します。これはリストのjoinにすぎません。

Haskellファンクターでは、2つのファンクターの構成はファンクターそのものです。擬似コードでは、次のように書くことができます。

instance (Functor f, Functor g) ⇒ Functor (f ∘ g) where
  fmap fun x = fmap (fmap fun) x

これは、joinf ∘ f → fからのマッピングとして考えるのに役立ちます。 joinのタイプは∀α. f (f α) → f αです。直感的に、all types αに有効な関数がfの変換と考えることができる方法を見ることができます。

returnも同様の変換です。タイプは∀α. α → f αです。これは異なって見えます-最初のαはファンクターに「入っていません」!幸いなことに、IDファンクタを追加することでこれを修正できます:∀α. Identity α → f αreturnは変換Identity → fです。

これで、モナドを、いくつかのファンクターfを基にした演算f ∘ f → fおよびIdentity → fに基づいた単なる代数と考えることができます。これはおなじみに見えませんか?これはモノイドと非常によく似ています。モノイドは、操作τおよびτ × τ → τを備えた() → τ型にすぎません。

したがって、モナドはモノイドに似ていますが、タイプを持つ代わりにファンクターがあります。これは同じ種類の代数であり、異なるカテゴリーに属しています。 (これは、私が知る限り、「モナドはエンドファンクターのカテゴリーのモノイドである」というフレーズの由来です。)

これで、f ∘ f → fIdentity → fの2つの操作ができました。対応する代数を取得するには、矢印を反転します。これにより、2つの新しい操作f → f ∘ ff → Identityが得られます。上記のように型変数を追加して、∀α. f α → f (f α)∀α. f α → αを与えることで、それらをHaskell型に変えることができます。これは、コモナの定義とまったく同じです。

class Functor f ⇒ Comonad f where
  coreturn ∷ f α → α
  cojoin   ∷ f α → f (f α)

したがって、共関数は、内積関数のカテゴリではcoalgebraになります。

465
Tikhon Jelvis

チュートリアルペーパー(co)代数と(co)帰納法に関するチュートリアルを読むと、コンピューターサイエンスの共同代数についての洞察が得られるはずです。

以下は、あなたを納得させるための引用です。

一般的に、あるプログラミング言語のプログラムはデータを操作します。過去数十年にわたるコンピューターサイエンスの発展の中で、たとえば、プログラムが動作するデータの特定の表現に依存しないようにするために、これらのデータの抽象的な記述が望ましいことが明らかになりました。また、このような抽象性は、正当性の証明を容易にします。
この欲求は、代数的仕様または抽象データ型理論と呼ばれる分岐で、コンピューターサイエンスで代数的手法を使用するようになりました。調査の対象は、代数からよく知られている手法の概念を使用した、それ自体がデータ型です。コンピュータ科学者が使用するデータ型は、多くの場合、指定された(コンストラクタ)操作のコレクションから生成されます。このため、代数の「初期性」が重要な役割を果たします。
標準的な代数的手法は、コンピューターサイエンスで使用されるデータ構造のさまざまな重要な側面をキャプチャするのに役立つことがわかっています。しかし、コンピューティングで発生する本質的に動的な構造の一部を代数的に記述することは困難であることが判明しました。このような構造には通常、状態の​​概念が含まれ、さまざまな方法で変換できます。そのような状態ベースの動的システムへの形式的なアプローチは、一般に、古典的な初期の参照として、オートマトンまたは遷移システムを利用します。
この10年の間に、そのような状態ベースのシステムは代数としてではなく、いわゆる共代数として記述されるべきであるという洞察が徐々に高まりました。これらは、このチュートリアルで正確になるように、代数の正式な双対です。代数の「初期性」の二重の性質、すなわち最終性は、そのような共代数にとって重要であることが判明しました。そして、そのような最終的な共代数に必要な論理的推論の原理は帰納ではなく、共帰納です。


プレリュード、カテゴリー理論についてカテゴリー理論は、ファンクターの理論の名前を変更する必要があります。カテゴリはファンクターを定義するために定義する必要があるものです。 (さらに、ファンクターは、自然な変換を定義するために定義する必要があるものです。)

ファンクタとはこれは、あるセットから別のセットへの変換であり、その構造を保持します。 (詳細については、ネット上で多くの良い説明があります)。

F代数とは?これはファンクターの代数です。これは、ファンクターの普遍的妥当性の研究に過ぎません。

どのようにコンピューターサイエンスにリンクできますか?プログラムは、構造化された一連の情報として見ることができます。プログラムの実行は、この構造化された情報セットの変更に対応します。実行はプログラム構造を保存するのが良いと思います。実行は、この情報セットに対するファンクターのアプリケーションとして表示できます。 (プログラムを定義するもの)。

なぜF-co-代数?プログラムは、情報によって記述され、それに基づいて行動するため、本質的に二重です。次に、主にプログラムを構成して変更する情報を2つの方法で表示できます。

  • プログラムによって処理されている情報として定義できるデータ。
  • プログラムが共有している情報として定義できる状態。

そして、この段階で、私はそれを言いたいです、

  • F代数は、データの宇宙(ここで定義)に作用する機能変換の研究です。
  • F共代数は、状態の宇宙に作用する機能変換の研究です(ここで定義されています)。

プログラムの存続期間中に、データと状態が共存し、それらが互いに完了します。彼らは二重です。

37
zurgl

明らかにプログラミングに関連するものから始めてから、数学的なものを追加して、できる限り具体的で現実的なものにします。


コインダクションに関するコンピューター科学者を引用しましょう…

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 examples of coalgebras by Alexandra Silva


周囲の数学的なコンテキストを通常のプログラミングタスクに関連付ける

「代数」とは何ですか?

代数構造は一般に次のようになります。

  1. Stuff
  2. ものができること

これは、1。プロパティと2.メソッドを持つオブジェクトのように聞こえます。または、さらに良いことに、タイプシグネチャのように聞こえるはずです。

標準的な数学的例には、モノイドidグループ⊃ベクトル空間⊃「代数」が含まれます。モノイドはオートマトンのようなものです:動詞のシーケンス(例えば、f.g.h.h.nothing.f.g.f)。常に履歴を追加し、履歴を削除しないgitログは、グループではなくモノイドになります。逆数(負の数、分数、根、蓄積された履歴の削除、破損したミラーの粉砕解除など)を追加すると、グループが得られます。

グループには、一緒に加算または減算できるものが含まれています。たとえば、Durationsを一緒に追加できます。 (ただしDatesは使用できません。)デュレーションは外部の数値でスケーリングできるため、(グループだけでなく)ベクトル空間で存続します。 (scaling :: (Number,Duration) → Durationの型シグネチャ。)

代数⊂ベクトル空間はさらに別のことを行うことができます:m :: (T,T) → Tがあります。 Integersを離れると、 "multiplication"(または "exponentiation" )がどうあるべきかがあまりわからなくなるため、これを「乗算」と呼びます。

(これが、人々が(カテゴリー理論)普遍的特性に目を向ける理由です:どの乗算がdoまたはのように

universal property of product )


代数→代数

共乗算は、乗算よりも、任意ではないように定義する方が簡単です。なぜなら、T → (T,T)から進むには、同じ要素を繰り返すことができるからです。 (「対角マップ」–スペクトル理論の対角行列/演算子のように)

通常、Counitはトレース(対角エントリの合計)ですが、ここでも重要なのは、Counitdoes; traceは、行列のちょうど良い答えです。

デュアルスペース を見る理由は、一般的に、そのスペースで考えるのが簡単だからです。たとえば、法線ベクトルについて考えるのは法線の平面よりも簡単な場合がありますが、ベクトルを使用して平面(超平面を含む)を制御できます(そして今ではレイトレーサーのようなおなじみの幾何学的ベクトルについて話します) 。


(非)構造化データの調整

数学者は TQFT's のような楽しいものをモデリングしているかもしれませんが、プログラマーは

  • 日付/時間(+ :: (Date,Duration) → Date)、
  • 場所(Paris(+48.8567,+2.3508)!これは形状であり、点ではありません。)、
  • ある意味で一貫しているはずの非構造化JSON
  • 間違っているが近いXML
  • 賢明な関係の負荷を満たす信じられないほど複雑なGISデータ、
  • meant何かを意味するが、Perlにとってはかなり少ない正規表現。
  • すべてのエグゼクティブの電話番号と別荘の場所、彼の(現在の元)妻と子供の名前、誕生日、および以前のすべてのギフトを保持する必要があるCRM。それぞれが信じられないほど「明白な」関係(顧客に明らか)を満たす必要がありますコード化が難しい、
  • .....

計算機科学者は、代数について話すとき、通常、デカルト積のように整然とした操作を念頭に置いています。これは人々が「代数はHaskellでの代数である」と言うときの意味だと思います。しかし、プログラマーがPlaceDate/Time、およびCustomerのような複雑なデータ型をモデル化し、それらのモデルを実際の世界(または少なくとも終わり-現実世界に対するユーザーの見解)可能な限り-デュアルは、集合世界だけでなく有用であると考えています。

4
isomorphismes