web-dev-qa-db-ja.com

モナドはエンドファンクターのカテゴリの中では単なるモノイドです、問題は何ですか?

誰が最初に次のように言いましたか?

モナドはエンドファンクターのカテゴリの中では単なるモノイドです、問題は何ですか?

そして、それほど重要ではないが、これは本当であり、もしそうなら、あなたは説明をすることができるだろう(うまくいけば、Haskellの経験があまりない人が理解できるもの)。

675

その特別な言い回しは、James Iryによるもので、彼は非常におもしろいプログラミング言語の簡潔で不完全かつほとんど間違った歴史で、架空のものと考えています。フィリップワドラーに。

オリジナルの引用はSaunders Mac Laneからのもので、カテゴリ理論の基本的なテキストの1つであるWorking Mathematician用のカテゴリです。 ここでは という文脈にありますが、これはおそらくそれが何を意味するのかを正確に学ぶための最良の場所です。

しかし、私は刺します。元の文はこれです:

つまり、XのモナドはXの内部関数の単なる単体であり、積×は内部関数の単位に置き換えられ、単位は単位固有関数によって設定されます。

Xこちらがカテゴリです。内部関数は、あるカテゴリからそれ自体へのファンクタです(関数型プログラマに関する限り、通常はallFunctorsですが、それらはほとんど1つのカテゴリだけを扱います;型のカテゴリですが - 私はdigress)しかし、あなたは「Xのendofunctors」のカテゴリーである別のカテゴリーを想像することができます。これは、対象が内部助言者であり、射が自然変換であるカテゴリです。

そしてそれらの内部関数のうち、それらのいくつかはモナドかもしれません。モナドはどれですか?特定の意味で正確にモノイドのもの。モナドからモノイドへの正確なマッピングを詳しく説明するのではなく(Mac Laneが望んでいたよりはるかに良いので)、それぞれの定義を並べて比較してみましょう。

モノイドは...

  • 集合、S
  • 演算、•:S×S→S
  • Se:1→Sの要素

...これらの法律を満たす:

  • (a•b)•c = a•(b•c)、すべてのabSの)c
  • e•a = a•e = a、すべてのaS

モナドは….

  • 内部関数、T:X→X(Haskellでは、Functorインスタンスを持つ種類* -> *の型コンストラクター)
  • 自然な変換、μ:T×T→Tここで、×はファンクター構成を意味します( μはHaskellではjoinとして知られています。)
  • 自然な変換、η:I→T、ここでIは恒等関数です。 onXηとして知られています。 return Haskellで)

...これらの法律を満たす:

  • μ○Tμ=μ○μT
  • μ○Tη=μ○ηT= 1(恒等自然変換)

ちょっと一言で言えば、これらの定義は両方とも同じ 抽象概念 のインスタンスであることがわかります。

737
Tom Crockett

直感的に、私は空想数学の語彙が言っているのはそれだと思う:

モノイド

モノイドはオブジェクトの集合とそれらを組み合わせる方法です。よく知られているモノイドは次のとおりです。

  • 追加できる番号
  • 連結できるリスト
  • あなたが団結することができます

もっと複雑な例もあります。

さらに、すべてのモノイドは同一性を持ちます。他のものと組み合わせると効果があります。

  • 0 + 7==7 + 0==7
  • [] ++ [1,2,3]==[1,2,3] ++ [] )==[1,2,3]
  • {}和集合{Apple}=={Apple}和集合{}=={Apple}

最後に、モノイドは結合的でなければなりません。 (オブジェクトの左から右への順序を変更しない限り、必要に応じて組み合わせの長い文字列を減らすことができます)加算はOKです((5 + 3)+1 == 5+(3+ 1))、しかし減算は((5-3)-1!= 5-(3-1))ではありません。

モナド

それでは、特別な種類のセットと、オブジェクトを組み合わせる特別な方法を考えてみましょう。

オブジェクト

あなたのセットが特別な種類のオブジェクトを含んでいるとしましょう:関数。そしてこれらの関数は興味深いシグネチャを持っています:それらは数字に数字を、文字列に文字列を運んでいません。代わりに、各関数は2段階のプロセスで番号のリストに番号を伝えます。

  1. 0個以上の結果を計算する
  2. どういうわけか、それらの結果を一つの答えにまとめてください。

例:

  • 1 - > [1](単に入力を折り返す)
  • 1 - > [](入力を破棄し、リストに何も入れないでください)
  • 1 - > [2](入力に1を加え、結果を折り返す)
  • 3 - > [4、6](inputに1を加え、inputに2を掛けて、複数の結果をラップする

オブジェクトを組み合わせる

また、私たちの関数の組み合わせ方は特別です。関数を組み合わせる簡単な方法は、合成です。上の例を見て、それぞれの関数を自分自身で構成しましょう。

  • 1 - > [1] - > [[1]](入力を2回折り返す)
  • 1 - > [] - > [](入力を破棄し、リストに何も入れないで2回)
  • 1 - > [2] - > [UH-OH! ](リストに "1"を追加することはできません!)
  • 3 - > [4、6] - > [UH-OH! ](リストに1を追加することはできません!)

型理論に深く入り込むことなしに、整数を得るために2つの整数を組み合わせることができるということですが、2つの関数を構成して同じ型の関数を得ることはできません。 (型a - > aを持つ関数は作成されますが、a-> [a]獲得 ' t。)

それでは、関数を組み合わせる別の方法を定義しましょう。これら2つの関数を組み合わせるとき、結果を "二重に折り返す"ことはしたくありません。

これが私たちのやり方です。 2つの関数FとGを結合したい場合は、次のプロセスに従います(bindingと呼ばれます)。

  1. Fからの「結果」を計算しますが、それらを組み合わせないでください。
  2. GをFの各結果に別々に適用した結果を計算し、結果のコレクションのコレクションを生成します。
  3. 2レベルコレクションを平坦化し、すべての結果を組み合わせます。

私たちの例に戻って、この新しい "束縛"関数を使って関数をそれ自身と結合(束縛)しましょう:

  • 1 - > [1] - > [1](入力を2回折り返す)
  • 1 - > [] - > [](入力を破棄し、リストに何も入れないで2回)
  • 1 - > [2] - > [3](1を加え、次に1を加え、結果を折り返す)
  • 3 - > [4,6] - > [5,8,7,12](inputに1を加え、両方の結果を保持したままinputを2で乗算し、それから両方の結果にもう一度すべてを実行し、最後にラップするリストになります。

関数を結合するためのこのより洗練された方法は連想的です(派手なラッピングをしていないときの関数構成が連想的である方法からわかるように)。

すべて一緒に結び付ける

  • モナドは、関数(の結果)を組み合わせる方法を定義する構造体です。
  • モノイドがオブジェクトを組み合わせる方法を定義する構造体であるのと同じように、
  • 結合方法が連想的な場合
  • また、何かと組み合わせることで、何かを変更せずに使用できる特別な「操作なし」がある場合。

ノート

結果を「ラップ」する方法はたくさんあります。リストやセットを作成したり、結果がないかどうかを確認しながら最初の結果以外のすべてを破棄したり、サイドカーの状態を添付したり、ログメッセージを印刷したりすることができます。

本質的なアイディアを直感的に理解してもらうために、定義を少し緩めてみました。

私たちのモナドはa - > [a]型の関数で動作すると主張することで物事を少し単純化しました。実際、モナドはa - > m bの型の関数に作用しますが、一般化は一種の技術的詳細であり、主な洞察ではありません。

503
misterbee

まず、使用する拡張機能とライブラリ:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)

これらのうち、RankNTypesは、以下に絶対に不可欠な唯一のものです。 私はかつてRankNTypesの説明を書いたが、一部の人々は有用だと思うようだ なので、それを参照する。

引用 Tom Crockettの優れた答え

モナドは...

  • エンドファンクター、T:X-> X
  • 自然な変換μ:T×T-> T、ここで×はファンクター構成を意味します
  • 自然な変換η:I-> T、ここでIX

...これらの法律を満たす:

  • μ(μ(T×T)×T))=μ(T×μ(T×T))
  • μ(η(T))= T =μ(T(η))

これをHaskellコードにどのように変換しますか?さて、自然変換の概念から始めましょう。

-- | A natural transformations between two 'Functor' instances.  Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
    Natural { eta :: forall x. f x -> g x }

f :-> gという形式の型は関数型に似ていますが、2つのtypesの間のfunctionと考える代わりに種類*)、morphism between two functors(kind * -> *のそれぞれ)と考えてください。例:

listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
    where go [] = Nothing
          go (x:_) = Just x

maybeToList :: Maybe :-> []
maybeToList = Natural go
    where go Nothing = []
          go (Just x) = [x]

reverse' :: [] :-> []
reverse' = Natural reverse

基本的に、Haskellでは、自然変換は、x型変数が呼び出し元に「アクセスできない」ような、ある型f xから別の型g xへの関数です。したがって、たとえば、sort :: Ord a => [a] -> [a]は、aに対してどの型をインスタンス化できるかについて「ピッキー」であるため、自然な変換にできません。これを考えるのによく使う直感的な方法の1つは次のとおりです。

  • ファンクターは、structureに触れることなく、何かのcontentを操作する方法です。
  • 自然な変換は、contentに触れたり見たりすることなく、何かのstructureを操作する方法です。

さて、これで邪魔にならないように、定義の条項に取り組みましょう。

最初の節は「エンドファンクター、T:X-> X」です。さて、HaskellのすべてのFunctorは、人々が「Haskカテゴリ」と呼ぶものの内的ファンクタであり、そのオブジェクトはHaskell型(種類*)であり、その形態はHaskell関数です。これは複雑なステートメントのように聞こえますが、実際には非常に簡単なステートメントです。それは、Functor f :: * -> *f a :: *および関数a :: *からfmap f :: f a -> f bの型f :: a -> bを構築する手段を提供するということだけです。これらがファンクターの法則に従うこと。

2番目の節:HaskellのIdentityファンクター(Platformに付属しているため、そのままインポートできます)は次のように定義されます。

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)

したがって、Tom Crockettの定義からの自然な変換η:I-> Tは、任意のMonadインスタンスtに対してこの方法で記述できます。

return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)

3番目の節:Haskellの2つのファンクターの構成は、この方法で定義できます(プラットフォームにも付属しています)。

newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose fga) = Compose (fmap (fmap f) fga)

したがって、自然変換μ:T×T-> Tは、トムクロケットの定義から次のように記述できます。

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)

これがエンドファンクターのカテゴリーのモノイドであるというステートメントは、Compose(最初の2つのパラメーターのみに部分的に適用される)が結合的であり、Identityがそのアイデンティティー要素であることを意味します。つまり、次の同型が成り立つこと:

  • Compose f (Compose g h) ~= Compose (Compose f g) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

ComposeIdentityは両方ともnewtypeとして定義されており、Haskellレポートはnewtypeのセマンティクスを、定義される型とnewtypeのデータコンストラクターへの引数の型の間の同型として定義するため、これらは非常に簡単に証明できます。たとえば、Compose f Identity ~= fを証明しましょう:

Compose f Identity a
    ~= f (Identity a)                 -- newtype Compose f g a = Compose (f (g a))
    ~= f a                            -- newtype Identity a = Identity a
Q.E.D.
80
Luis Casillas

この投稿には、Mac Laneの悪名高い引用の推論をよりよく理解するために来ました作業数学者のカテゴリー理論

何かを説明する際に、そうでないものを説明することもしばしば有用です。

Mac Laneがモナドを記述するために記述を使用しているという事実は、モナドに固有の何かを記述することを暗示しているかもしれません。我慢してください。声明をより広く理解するには、彼がnotモナド特有の何かを記述していることを明確にする必要があると思います。このステートメントは、特にApplicativeとArrowsを説明しています。同じ理由で、Intに2つのモノイド(SumとProduct)があり、endofunctorのカテゴリのXにいくつかのモノイドがあります。しかし、類似点にはさらに多くのものがあります。

MonadとApplicativeの両方が基準を満たしています:

  • endo =>任意の矢印、または同じ場所で開始および終了する射
  • functor =>任意の矢印、または2つのカテゴリ間の射影

    (例:毎日Tree a -> List b、ただしカテゴリTree -> List

  • モノイド=>単一オブジェクト;つまり、単一のタイプですが、このコンテキストでは、外部レイヤーに関してのみです。したがって、Tree -> Listは使用できず、List -> Listのみを使用できます。

ステートメントは「...のカテゴリ」を使用します。これにより、ステートメントの範囲が定義されます。例として、Functorカテゴリーはf * -> g *のスコープ、つまりAny functor -> Any functor、例えばTree * -> List *またはTree * -> Tree *を記述します。

カテゴリステートメントで指定されていないものは、何でもすべてが許可されているの場所を示しています。

この場合、ファンクター内で、* -> *別名a -> bは指定されていません。これはAnything -> Anything including Anything elseを意味します。私の想像力はInt-> Stringにジャンプするので、Integer -> Maybe Int、またはMaybe Double -> Either String Intが含まれており、a :: Maybe Double; b :: Either String Intになっています。

したがって、ステートメントは次のようにまとめられます。

  • functor scope :: f a -> g b(つまり、パラメーター化された型からパラメーター化された型へ)
  • endo + functor :: f a -> f b(つまり、1つのパラメーター化された型から同じパラメーター化された型へ)...
  • エンドファンクターのカテゴリーのモノイド

それで、この構造の力はどこにあるのでしょうか?完全なダイナミクスを理解するために、モノイド(アイデンティティ矢印:: single object -> single objectのように見える単一のオブジェクト)の典型的な描画では、anyでパラメーター化された矢印の使用が許可されていることを説明できません。数モノイドの値、oneモノイドで許可されたタイプオブジェクトから。エンド、同等性の〜アイデンティティ矢印の定義ignoresファンクタのtype valueおよび最も内側の「ペイロード」層のタイプと値の両方。したがって、関数型が一致するすべての状況で等価はtrueを返します(たとえば、Nothing -> Just * -> NothingJust * -> Just * -> Just *であるためMaybe -> Maybe -> Maybeと同等です)。

サイドバー:〜outsideは概念的ですが、f aの左端のシンボルです。また、「Haskell」が最初に読み込む内容についても説明します(全体像)。そのため、TypeはType Valueに関して「外部」です。プログラミングにおけるレイヤー間の関係(一連の参照)は、カテゴリーに関連付けるのは簡単ではありません。セットのカテゴリは、ファンクタのカテゴリ(パラメータ化されたタイプ)を含むタイプ(Int、文字列、多分Intなど)を記述するために使用されます。参照チェーン:Functor Type、Functor values(そのFunctorのセットの要素、例えばNothing、Just)、そして順番に、各functor値が指すすべてのもの。カテゴリでは、関係の記述が異なります。たとえば、return :: a -> m aは、これまでに言及したものとは異なり、あるFunctorから別のFunctorへの自然な変換と見なされます。

定義されたテンソル積とニュートラル値のすべてについて、メインスレッドに戻ると、ステートメントは、その逆説的な構造から生まれた驚くほど強力な計算構造を説明することになります。

  • 外側では、単一のオブジェクトとして表示されます(例::: List);静的
  • しかし、内部では、多くのダイナミクスを許可します
    • 任意のアリティの関数への飼料として、同じタイプの値(Empty |〜NonEmptyなど)の任意の数。テンソル積は、任意の数の入力を単一の値に減らします...外部層(〜foldはペイロードについて何も語らない)
    • bothの無限の範囲は、最も内側のレイヤーのタイプと値

Haskellでは、ステートメントの適用可能性を明確にすることが重要です。この構造の力と汎用性は、モナドとはまったく関係ありませんper se。言い換えれば、この構造はモナドを一意にするものに依存しません。

相互に依存する計算をサポートするために共有コンテキストでコードを構築するかどうかを把握しようとするとき、並行して実行できる計算とは対照的に、この悪名高いステートメントは、説明する限りでは、 Applicative、Arrows、およびMonadsですが、どちらが同じかを説明しています。手元の決定については、声明は論争です。

これはしばしば誤解されます。この文は、join :: m (m a) -> m aをモノイド内部機能のテンソル積として説明しています。ただし、このステートメントのコンテキストで、(<*>)も選択された可能性があることを明確に示していません。それは本当に6半ダースの例です。値を組み合わせるためのロジックはまったく同じです。同じ入力は、それぞれから同じ出力を生成します(Intを結合すると異なる結果を生成するため、IntのSumおよびProductモノイドとは異なります)。

要約すると、エンドファンクターのカテゴリーのモノイドは次のように説明します。

   ~t :: m * -> m * -> m *
   and a neutral value for m *

(<*>)(>>=)は両方とも、単一の戻り値を計算するために、2つのm値への同時アクセスを提供します。戻り値の計算に使用されるロジックはまったく同じです。パラメーター化する関数の異なる形状(f :: a -> bk :: a -> m b)および計算の同じ戻り値型を持つパラメーターの位置(つまり、それぞれa -> b -> bb -> a -> b)でなかった場合、パラメーター化した可能性があります両方の定義で再利用するためのモノイド論理、テンソル積。要点を確認するための演習として、~tを試して実装すると、(<*>)の定義方法に応じて(>>=)forall a bになります。

私の最後のポイントが概念的に少なくとも真である場合、ApplicativeとMonadの間の正確で唯一の計算上の違い、つまりパラメーター化する関数について説明します。つまり、これらの型クラスの実装との違いはexternalです。

結論として、私自身の経験では、Mac Laneの悪名高い引用は、偉大な "goto"ミームを提供しました。これは、Haskellで使用されるイディオムをよりよく理解するためにCategoryをナビゲートしながら参照するための指針です。 Haskellで見事にアクセスできる強力なコンピューティング能力の範囲を獲得することに成功しました。

ただし、モナド以外でのステートメントの適用可能性を最初に誤解した方法と、ここで伝えたいことには皮肉があります。それが記述するすべては、ApplicativeとMonads(およびとりわけArrows)の間で類似しているものであることが判明しました。それが言っていないのは、正確には小さいながらも有用な違いです。

-E

6
Edmund's Echo

注:いいえ、これは正しくありません。ある時点で、Dan Piponi自身からのこの回答に対するコメントがあり、ここでの原因と結果は正反対であり、James Iryの質問に答えて彼の記事を書いたということです。しかし、それはおそらく強制的な片付けによって、取り除かれたようです。

以下が私の最初の答えです。


HaskellでDan Piponi(sigfpe)がモノイドからモナドを派生させた記事で、Iryが モノイドからモナドへ を読んだことは十分にあり得ます。カテゴリ理論と " Hask 上の内部関数のカテゴリ"の明示的な言及。いずれにせよ、モナドがエンドファンクターのカテゴリのモノイドであることが何を意味するのか疑問に思う人は誰でも、この派生を読むことから利益を得るかもしれません。

5
hobbs