Haskカテゴリーの内包機能とそのモノイド構造

はじめに



前の記事で、Haskell言語のデータ型と機能で構成されるHaskカテゴリーのコンテキストにおけるカテゴリーとファンクターの概念について説明しました。 ここで、すでに知られている概念から構築されたカテゴリの別の例と、モノイドの非常に重要な概念について説明します。



指定



前回、文字f



でモーフィズム/関数を示したいと思っていましたが、タイプf



ファンクター/変数を示すために使用されました-Haskell言語の観点からは問題はありませんが、注意深く読むと混乱しやすく、文字をモーフィズムに使用しましたg



。 些細なことですが、それでも、性質の異なるエンティティを視覚的に分離することは有益だと思います。 私は普通の名前で普通の型を呼び出しますが、型変数はアルファベットの先頭から単純な(



)文字、アルファベットの最後からパラメトリック( ∗ → ∗



)文字(末尾からではなくθ



X



あまりにも似ているχ



よりも良く見えます。 そのため、 Haskカテゴリの用語では:

GHCはかなり長い間ユニコードをサポートしてきたという事実により、これらの表記法は構文に関して何も変更せず、本質的に純粋に表面的なものです。



用語に関する別の発言:既にお気づきのように、私が最後に「種類」という言葉を呼んだもの(今)、私は今、「ソート」という言葉を呼んでいます-これは一般に受け入れられている翻訳と見なされます。



Haskカテゴリ



Haskカテゴリー自体というオブジェクトが1つだけ存在するカテゴリーを見てみましょう。 このカテゴリの射とは何ですか? これらは何らかの種類のマッピングHask →Haskである必要があり、このタイプのマッピングを既に知っています-これらはHaskカテゴリの内的ファンクター、つまりFunctor



クラスの実施形態である多様性∗ → ∗



タイプです。 ここで、公理を満たすために、このカテゴリで単一の射と構図をどのように配置するかを考える必要があります。



アイデンティティファンクター



単位射であるアイデンティティファンクター、つまり、何も変更しないファンクターを選択するのは自然です。 Haskellでは、特別な指定はありません-何もしないので、なぜそれを指定する必要があるのですか? Haskカテゴリーのオブジェクトに対するこのファンクターのアクションを「視覚化」できる「ダミー」タイプのコンストラクターを紹介します。

 type Id α = α
      
      





Haskellでは、型シノニムのインカネーションを宣言できないため、他のファンクターとまったく同じ方法でId



を入力することはできませんが、可能であれば、これを記述します。

 -- warning: - instance Functor Id where fmap ∷ (α → β) → (Id α → Id β) fmap f = f
      
      



つまり、射手のこのファンクターの動作は、 fmap ≡ id ∷ (α → β) → (α → β)



-ここにはコンストラクターId



はありませんが、これは同じシグネチャです。 したがって、2つのファンクターマッピングは次のようになります。

 α ∷ ∗ ↦ Id α = α ∷ ∗ f ∷ α → β ↦ id f = f ∷ α → β
      
      





このファンクタは役に立たないように見えますが、カテゴリの定義を完了するために必要です。 さらに、任意のタイプのα ∷ ∗



α ∷ ∗



として、つまり同一のファンクターの作用下で認識することを可能にします。これは、モナドと出会うときに依然として有用です。



ファンクターの構成



私たちが検討しているカテゴリーでは、射(内ファンクター)の構成だけが欠けています。 1つのオブジェクトの射を考慮するため、それらはすべて同じ領域と共領域を持っているため、合成は任意の2つの射に適用できることをアプリオリに知っています。



例から始めましょう。 私たちに知られている2つのファンクター、 Maybe



[]



を考えてください。 オブジェクトに対する動作は次のとおりです。

 α ∷ ∗ ↦ Maybe α ∷ ∗ β ∷ ∗ ↦ [β] ∷ ∗
      
      





構成とは、最初に1つのマッピングを適用し、結果に2番目のマッピングを適用することを意味します。

 α ↦ Maybe α ↦ [Maybe α]
      
      



または別の順序で:

 α ↦ [α]Maybe [α]
      
      



タイプコンストラクターに構成を適用するために、特別なものは何も必要ありません-最初に1つ、次にもう1つを書くだけです。 この操作のために、通常の構成に似た表記法を導入します(演算子の形式の型コンストラクターはコロンで始める必要があり、2番目は対称性のために置きます)。

 type (φ :.: ψ) α = φ (ψ α)
      
      





次に、射のマッピングで何が起こるか見てみましょう。 このファンクターの名前はすべてのファンクターに対してfmap



ため、一般に、1つのfmap



をモルフィズムに適用する必要があり、2番目のファンクター、つまりファンクターの構成のマッピングはλf → fmap (fmap f)



またはfmap . fmap



fmap . fmap



これら2つのfmap



(1つの多態性関数の異なる仮説)には、それぞれ独自の型と対応する定義があることを理解する必要があります。 これを明確に示しましょう。

 instance Functor Maybe where -- fmap ∷ (α → β) → (Maybe α → Maybe β) fmap f = f' where f' Nothing = Nothing f' (Just x) = Just (fx) instance Functor [] where -- fmap ∷ (α → β) → ([α] → [β]) fmap g = g' where g' [] = [] g' (x:xs) = (gx) : (g' xs)
      
      





次に例を使用して構成を検討し[] :.: Maybe





 (fmap . fmap) ∷ (α → β) → ([Maybe α] → [Maybe β]) (fmap . fmap) even [Just 2, Nothing, Just 3] ≡ [Just True, Nothing, Just False]
      
      





そして別の順序で: Maybe :.: []





 (fmap . fmap) ∷ (α → β) → (Maybe [α] → Maybe [β]) (fmap . fmap) even Just [1, 2, 3] ≡ Just [False, True, False] (fmap . fmap) even NothingNothing
      
      





ファンクターの構成をFunctor



クラスの具体化にしたい場合、 TypeComposeパッケージの作成者が行うのとほぼ同じ方法で行います 。 ただし、値を追加のデータコンストラクターでラップする必要があるため、これにはあまり意味がありません。 したがって、ファンクタをマップのペアとして(オブジェクトと射影上で)話す場合、2つのファンクタ(φ, fmap



)



および(ψ, fmap



)



場合、その構成はマップのペアになります: (φ :.: ψ, fmap



)



、すなわち



α ∷ ∗ ↦ (φ :.: ψ) α ∷ ∗





f ∷ α → β ↦ (fmap



) f ∷ (φ :.: ψ) α → (φ :.: ψ) β







正式には、このマッピングのペア自体がHaskカテゴリの内部機能であることを確認する必要があります。つまり、単一のモーフィズムとモーフィズムの構成を保持しますが、1つのfmap



が保存されるため、2つの連続して適用されるfmap



が保存されます:

 (fmap . fmap) id ≡ fmap (fmap id) ≡ fmap idid (fmap . fmap) (f . g) ≡ ≡ fmap (fmap (f . g)) ≡ ≡ fmap ((fmap f) . (fmap g)) ≡ ≡ (fmap (fmap f)) . (fmap (fmap g)) ≡ ≡ ((fmap . fmap) f) . ((fmap . fmap) g)
      
      



証明するために必要でした。 ファンクターと同一のファンクターの構成は、自然でシンプルな構造ですが、それでも有用ですが、モナドに到達したときに再び必要になります。



そのため、Haskオブジェクトと内包関数を射として使用してカテゴリを構築するには、2つの公理をチェックする必要があります。

すべて順調です!



モノイド構造



実際、上記のカテゴリはモノイドであるため、モノイドとの知り合いはすでに発生しています。 それに関する冗長な情報を除外し、定義のみを残しましょう。

モノイドは単一のオブジェクトカテゴリです



とても簡単です。 この言葉遣いから、「モノ」とは「1」という意味の名前がす​​ぐに明らかになります。



カテゴリーモノイドのもう1つの例を示します。 1つのInt



持つHaskカテゴリのサブカテゴリを考えます。 射として、次の形式の関数をとりますλn → n + k



または各k ∷ Int



(+k)



(+100500)



、すなわち(+(-1))



(+7)



(+100500)



(+7)



(+100500)



など 通常の構成と(+0) ≡ id



とともに、カテゴリが取得されます。 次のように、数値行のInt



型のすべての値と、これらの射の影響を想像できます。

  Int: … -5 -4 -3 -2 -1 0 1 … (+(-2)) ∷ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Int: … -3 -2 -1 0 1 2 3 … (+3) ∷ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ Int: … 0 1 2 3 4 5 6
      
      



つまり、視覚的には、これらは指定された数の位置による原点( 0



)のシフトです。ただし、一般的に数値の線は同じままです。 カテゴリの構造を提供するもの:最初に、すべてを所定の場所に残す単一のモーフィズムがあります。次に、モーフィズムの構成があります。それは連想的であり、 id



中立です。 m



n



が2つの整数の場合、構成は次のとおりです。

 (+m) . (+n) ≡ (+(m+n))
      
      



つまり、実際には合計として機能します。 各モルフィズム(+m)



を単純に数値m



(原点を移動した場所: (+m) 0 ≡ m



と考えることができ、「組成」 m



およびn



は実際にはそれらの合計です: (m+n)



、そして、単一の射は単純にゼロです。



このビューは、モノイドの集合論的定義に対応しています。

モノイドは、セット、このセットでの連想バイナリ操作、およびこの操作に関して中立な要素で構成されるトリプルです。



この例では、これはそのようなトリプルです: (Int, (+), 0)





 (l + m) + n ≡ l + (m + n) m + 0 ≡ m ≡ 0 + m
      
      





この定義では、カテゴリの公理は単純に再定式化されます。集合は射の集合であり、連想二項演算は射の合成であり、中立要素は単一の射です。 モノイドの構造をどう見ても、主なことは二項演算中立要素の存在であるということを理解することが重要です。



HaskellのMonoid



クラスを見てみましょう。

 class Monoid μ where mappend ∷ μ → μ → μ mempty ∷ μ
      
      



ここで、 μ



は検討中のセット、 mappend



は連想的である必要がある操作、 mempty



mappend



に関して中立でなければならない要素です。 トリプル(Int, (+), 0)



、このクラス(Int, (+), 0)



実施形態は直接になります。

 instance Monoid Int where mappend = (+) mempty = 0
      
      





同様に、他のトリプル、たとえば(Float, (*), 1)



または([α], (++), [])



または(Bool, (||), False)



、ここで||



-論理的な「または」。 しかし、再びカテゴリ間の関係を示すカテゴリ定義を実現できます。

 type Mor α = α → α instance Monoid (Mor α) where mappend = (.) mempty = id
      
      



この場合、タイプα ∷ ∗



Haskカテゴリーのオブジェクトであり、タイプMor α



関数はこのオブジェクトの射である。 通常の構成と単一のモルフィズムid



とともに、1つのオブジェクト、つまりモノイドでカテゴリを形成します。



Data.Monoid



モジュールのEndo



タイプにも同様のインカネーションがあります。 ちなみに、このモジュールのほとんどすべての化身は、「ラッパー」の型用に作成されています。 これは、同じセットでモノイド構造をさまざまな方法で導入できるためです。 たとえば、 Int



型の場合、考慮される構造(Int, (+), 0)



Sum



参照)に加えて、 (Int, (*), 1)



Product



参照)、およびBool



型の場合、前述の(Bool, (||), False)



Any



参照)は(Bool, (&&), True)



All



参照)です。



そのようなトリプルモノイドの多くの例は、Habrの記事 Monoids and their Applications:Monoidal Computing in Trees」にあります。 さらに、Haskellでのモノイドの使用については、ジャーナルFunctional Programming Practiceの 翻訳記事を読むことができます。 ええと、写真がなくて本当に悲しい場合は、 「Haskell for Great Good」教科書のモノイドに関する章の翻訳があり ます!



おわりに



読者には、これらのモノイドのさまざまな定義ですべてを逆さまにしたように見えるかもしれませんが、構造の重要性ではなく、その上に構築されたものではなく、セットの要素、カテゴリのモーフィズム、または他の何かに重要であることを示したかっただけです。



実際、この理論全体を通して、さまざまな種類の矢印(マッピング、変換)について話しています。 単一の射を持つオブジェクトを識別できますが、それ以上の構成ではオブジェクトについて詳しく話すことはできません(指定として単一の射のインデックス付けのみが必要であり、射の領域と共領域は構図の正しい構成にのみ必要です)。



オブジェクト間の矢印としての射についてはすでに知っています。カテゴリ間の矢印のようなファンクターについては知っています。 そして、ファンクター間の矢印の性質は何ですか? これらの矢印は自然変換と呼ばれ、次回それらについて説明します。



この記事を要約するために、検討した主な概念をもう一度繰り返し、それらをリンクさせたいと思います。

Haskカテゴリの内手と関手の構成および同一の関手はモノイド構造を持っています



このキーフレーズは、ファンクターと自然な変換から構築する(厳密に)モノイダルカテゴリに関して非常に有用です。



All Articles