誰が最初に次のことを言ったのですか?
モナドはエンドファンクターのカテゴリーの単なるモノイドですが、何が問題なのですか?
そして、それほど重要ではないメモで、これは本当ですか?もしそうなら、説明を提供できますか(願わくば、Haskellの経験があまりない人でも理解できると思います)。
誰が最初に次のことを言ったのですか?
モナドはエンドファンクターのカテゴリーの単なるモノイドですが、何が問題なのですか?
そして、それほど重要ではないメモで、これは本当ですか?もしそうなら、説明を提供できますか(願わくば、Haskellの経験があまりない人でも理解できると思います)。
回答:
その特定の言い回しは、ジェームズ・アイリーによる、彼の非常に面白いブリーフ、不完全、そしてほとんど間違ったプログラミング言語の歴史からのものであり、彼はフィクションとしてそれをフィリップ・ワドラーに帰している。
元の引用は、カテゴリー理論の基礎となるテキストの1つである「働く数学者のためのカテゴリー」の Saunders Mac Laneによるものです。ここがコンテキストです。これが意味するところを正確に理解するのにおそらく最適な場所です。
しかし、私は刺します。元の文はこれです:
結局のところ、XのモナドはXの内部関数のカテゴリの単なるモノイドであり、製品×は内部関数の構成と単位関数によって識別されるユニットで置き換えられています。
ここのXはカテゴリーです。Endofunctorは、カテゴリーからそれ自体へのファンクターです(通常、関数型プログラマーに関する限り、すべて Functor
のs です。これは、主に1つのカテゴリーのみを扱っているためです。タイプのカテゴリーですが、私は余談です)。しかし、「endofunctors on X」のカテゴリーである別のカテゴリーを想像できます。これは、オブジェクトが内部関数であり、射が自然変形であるカテゴリです。
そして、それらの内部関数の中で、それらのいくつかはモナドかもしれません。モナドはどれですか?厳密には、特定の意味でモノイドであるもの。モナドからモノイドへの正確なマッピングを詳しく説明する代わりに(Mac Laneは私が期待するよりもはるかに優れているため)、それぞれの定義を並べて比較します。
* -> *
、Functor
インスタンスを持つ種類の型コンストラクター)join
Haskellで知られています)return
Haskellで知られています)少し目を細めると、これらの定義の両方が同じ抽象概念のインスタンスであることがわかるでしょう。
S
、型である場合、関数を作成するときにできることは、型のf :: () -> S
特定の用語S
(ある場合は、その「要素」)を選択して返すということです。それ...あなたは引数に関して実際の情報を与えられていないので、関数の振る舞いを変える方法はありません。したがってf
、毎回同じものを返すだけの定数関数でなければなりません。()
(「ユニット」)はカテゴリHaskの最終オブジェクトであり、そこに生息する値が1(非発散)であるのは偶然ではありません。
直感的に、私は空想数学の語彙が言っていることは次のとおりだと思います:
モノイドはオブジェクトのセット、及びそれらを組み合わせた方法です。よく知られているモノイドは次のとおりです。
さらに複雑な例もあります。
さらに、すべてのモノイドにはIDがあります。これは、 "no-op"要素であり、他のものと組み合わせても効果がありません。
最後に、モノイドは連想的でなければなりません。(オブジェクトの左から右の順序を変更しない限り、必要に応じて組み合わせの長い文字列を減らすことができます)追加はOK((5 + 3)+1 == 5+(3+ 1))、ただし減算は((5-3)-1!= 5-(3-1))ではありません。
ここで、特別な種類のセットとオブジェクトを組み合わせる特別な方法を考えてみましょう。
セットに特別な種類の関数(関数)が含まれているとします。そして、これらの関数には興味深いシグネチャがあります。これらは、数値を数値に、または文字列を文字列に運びません。代わりに、各関数は2段階のプロセスで数値のリストに数値を渡します。
例:
また、関数を組み合わせる方法も特別です。関数を組み合わせる簡単な方法は合成です。上記の例を見て、各関数をそれ自体で合成してみましょう。
型理論をあまり理解しなくても、ポイントは2つの整数を組み合わせて整数を取得できることですが、2つの関数を常に合成して同じ型の関数を取得できるとは限りません。(型を持つ関数A - > 構成されますが、A-> [A]ません)。
それでは、関数を組み合わせる別の方法を定義しましょう。これらの関数の2つを組み合わせる場合、結果を「二重ラップ」する必要はありません。
これが私たちの仕事です。2つの関数FとGを組み合わせる場合は、次のプロセスに従います(バインディングと呼ばれます)。
例に戻り、関数を「バインド」するこの新しい方法を使用して、関数をそれ自体と結合(バインド)します。
関数を組み合わせるこのより洗練された方法は、関連性があります(ファンシーなラッピングを行っていないときの関数構成の関連性に続きます)。
それをすべて一緒に結ぶ、
結果を「ラップ」する方法はたくさんあります。リストやセットを作成したり、最初の結果を除いてすべて破棄したり、結果がないかどうかを確認したり、状態のサイドカーを添付したり、ログメッセージを印刷したりできます。
私は本質的なアイデアを直感的に理解することを期待して、定義を少し緩めました。
モナドがタイプa- > [a]の関数を操作するように主張することで、少し単純化しました。実際、モナドはタイプa-> mbの関数で機能しますが、一般化は技術的な詳細の一種であり、主要な洞察ではありません。
a -> [b]
を作成することはできませんc -> [d]
(b
=の場合にのみこれを実行できますc
)ため、これはモノイドを完全に記述していません。これは実際には、「モノイド演算子」である関数構成ではなく、説明した平坦化操作です。
a -> [a]
、これはモノイドになります(Kleisliカテゴリを単一のオブジェクトに減らし、任意のカテゴリのオブジェクトを1つだけにするため)定義によりモノイドです!)、しかしそれはモナドの完全な一般性を捕らえません。
まず、使用する拡張機能とライブラリ:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
これらのうちRankNTypes
、以下に絶対に不可欠な唯一のものです。私はかつてRankNTypes
一部の人々が有用だと思ったとの説明を書いたので、それを参照します。
トムクロケットの素晴らしい答えを引用して、
モナドは...
- 内部ファンクター、T:X-> X
- 自然な変換μ:T×T-> T、ここで×はファンクター組成を意味します
- 自然な変換、η:I-> T、ここでIはXの恒等内関数です
...これらの法律を満たす:
- μ(μ(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つのタイプ(種類の)の間の関数として考えるのではなく、2つのファンクタ(それぞれの種類)の間の射として考えます。例:*
* -> *
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では、自然な変換は、あるタイプf x
から別のタイプへの関数g x
であり、x
タイプ変数は呼び出し側から「アクセス不可能」です。したがって、たとえば、sort :: Ord a => [a] -> [a]
インスタンス化できる型について「ピッキー」であるため、自然な変換に変換することはできませんa
。私がこれを考えるのによく使用する直感的な方法の1つは次のとおりです。
さて、それが邪魔にならないように、定義の節に取り組みましょう。
最初の節は「内部関数T:X-> X」です。さて、Functor
Haskellのすべては、人々が「Haskカテゴリー」と呼ぶものの内部関数であり、そのオブジェクトはHaskell型(種類*
)であり、その形態はHaskell関数です。これは複雑なステートメントのように聞こえますが、実際には非常に簡単なものです。それが意味すべてがあることがあることであるFunctor f :: * -> *
あなたのタイプを構築する手段与えf a :: *
いずれかのa :: *
機能fmap f :: f a -> f b
の任意の外にf :: a -> b
、これらのオベイファンクタ法則という。
2番目の句:Identity
Haskell のファンクター(プラットフォームに付属しているため、インポートできる)は次のように定義されます。
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
したがって、自然変形η: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)
したがって、Tom Crockettの定義による自然変換μ: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
これらは両方ともとして定義されているためCompose
、証明が非常に簡単です。また、Haskellレポートは、のセマンティクスを、定義されている型とのデータコンストラクターへの引数の型の間の同型として定義します。たとえば、証明してみましょう:Identity
newtype
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.
Natural
newtypeの、私は何を把握することはできません(Functor f, Functor g)
制約がやっています。説明してもらえますか?
Functor
制約は必要ないと思われるため、削除しました。同意しない場合は、自由に追加してください。
join
が定義される場合のみです。そして、それjoin
が射射射です。確信はないけど。
注:いいえ、これは正しくありません。ある時点で、Dan Piponi自身からのこの回答に関するコメントがあり、ここでの原因と結果は正反対であり、James Iryの冗談に応えて彼の記事を書いたとのことです。しかし、おそらく一部の強迫的な整頓によって、それは削除されたようです。
以下は私の元の答えです。
それはIRYが読んでいたことは非常に可能ですモノイドからモナドに、ダンPiponi(SIGFPE)は圏論と「上endofunctorsのカテゴリの明示的な言及の多くの議論で、Haskellでモノイドからモナドを導出するポストHask」。いずれにせよ、モナドが内部ファンクターのカテゴリーでモノイドであることが何を意味するのか疑問に思う人なら誰でも、この派生を読むことで利益を得られるでしょう。
:-)
。
私は、Mac Laneの「動作する数学者のためのカテゴリー理論」からの悪名高い引用の推論をよりよく理解するためにこの投稿に来ました。
何かが何であるかを説明する際、そうでないものを説明することも同様に役立ちます。
Mac Laneがモナドを説明するために説明を使用しているという事実は、モナドに固有の何かを説明していることを意味するかもしれません。私と一緒に耐えなさい。声明をより広く理解するためには、彼がモナドに固有の何かを説明していないことを明確にする必要があると思います。この声明は、特にApplicativeとArrowsについて説明しています。同じ理由で、Int(SumとProduct)に2つのモノイドを持つことができます。endofunctorsのカテゴリでは、Xにいくつかのモノイドを持つことができます。しかし、類似点はさらにあります。
モナドとアプリケーションの両方が基準を満たしています:
(例:毎日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
where も含まれa :: Maybe Double; b :: Either String Int
ます。
したがって、ステートメントは次のようにまとめられます。
:: f a -> g b
(つまり、任意のパラメータ化された型から任意のパラメータ化された型へ):: f a -> f b
(つまり、パラメータ化された1つの型から同じパラメータ化された型へ)...別の言い方をすると、では、この構造の力はどこにあるのでしょうか?完全なダイナミクスを理解するために、モノイド(識別矢印のように見える単一のオブジェクト)の典型的な描画が、任意の数のモノイド値で:: single object -> single object
パラメーター化された矢印の使用が許可されていることを説明できないことを確認する必要がありました。Monoidで許可されている1つのタイプのオブジェクトから。等価の内部、〜識別矢印の定義では、ファンクターの型の値と、最も内側の「ペイロード」レイヤーの型と値の両方が無視されます。したがって、関数型が一致するすべての状況で等価性が返されます(たとえば、両方がであるため、と同等です)。true
Nothing -> Just * -> Nothing
Just * -> Just * -> Just *
Maybe -> Maybe -> Maybe
サイドバー:〜外は概念的なものですが、の左端の記号ですf a
。また、「Haskell」が最初に読み込む内容(全体像)についても説明します。そのため、TypeはType値に対して「外側」になります。プログラミングにおけるレイヤー間の関係(参照のチェーン)は、カテゴリーで関連付けるのは容易ではありません。セットのカテゴリは、ファンクタのカテゴリ(パラメータ化されたタイプ)を含むタイプ(Int、文字列、多分Intなど)を記述するために使用されます。参照チェーン:Functor Type、Functor値(そのFunctorのセットの要素、たとえば、Nothing、Just)、および各ファンクター値が指す他のすべて。カテゴリでは、関係の記述方法が異なります。たとえば、return :: a -> m a
あるFunctorから別のFunctorへの自然な変換と見なされ、これまでに述べたものとは異なります。
メインスレッドに戻ると、すべての定義済みテンソル積とニュートラル値について、ステートメントは、その逆説的な構造から生まれた驚くほど強力な計算構造を説明することになります。
:: List
。静的fold
ペイロードについては何も言わない)Haskellでは、ステートメントの適用可能性を明確にすることが重要です。この構造の能力と汎用性は、モナド自体とはまったく関係ありません。言い換えれば、構造はモナドをユニークにするものに依存しません。
互いに依存する計算をサポートするために共有コンテキストでコードを構築するかどうか、並列に実行できる計算を比較するかどうかを検討する場合、この悪名高いステートメントは、説明されているとおり、選択の違いではありませんApplicative、Arrows、Monadsですが、どれだけ同じかを説明しています。目前の決定については、陳述は意味がない。
これはよく誤解されています。声明はjoin :: m (m a) -> m a
さらに、モノイドの内部ファンクターのテンソル積として記述されています。ただし、このステートメントのコンテキストで、どのように(<*>)
選択することもできるかについては明確に述べていません。それは本当に6分の6の例です。値を組み合わせるロジックはまったく同じです。同じ入力はそれぞれから同じ出力を生成します(Intを組み合わせるときに異なる結果を生成するため、IntのSumおよびProductモノイドとは異なります)。
つまり、要約すると、エンドファンクターのカテゴリーのモノイドは、
~t :: m * -> m * -> m *
and a neutral value for m *
(<*>)
そして、(>>=)
の両方が2つのへの同時アクセスを提供m
単一の戻り値を計算するために値を。戻り値の計算に使用されるロジックはまったく同じです。それは彼らが(パラメータ化機能の異なる形状がなければf :: a -> b
対k :: a -> m b
)と計算の同じ戻り値の型とパラメータの位置(すなわち、a -> b -> b
対b -> a -> b
ごとに、それぞれ)、私は、我々はmonoidalロジックをパラメータ化している可能性が疑われます両方の定義で再利用するためのテンソル積。ポイントを作るための練習として、試してみて、実装~t
、およびあなたが終わる(<*>)
と、(>>=)
あなたはそれを定義することを決定した方法に応じてforall a b
。
私の最後の点が少なくとも概念的に真である場合、それはApplicativeとMonadの間の正確で計算上の違いのみを説明します:それらがパラメーター化する関数。言い換えれば、違いはこれらの型クラスの実装の外部にあります。
結論として、私自身の経験では、Mac Laneの悪名高い引用は素晴らしい「後藤」ミームを提供しました。これは、Haskellで使用されているイディオムをよりよく理解するためにカテゴリをナビゲートするときに参照する道標です。Haskellで見事にアクセス可能になった強力なコンピューティング能力の範囲をキャプチャすることに成功しました。
しかし、モナド外でのステートメントの適用性を最初に誤解した方法と、ここで伝えたいことには皮肉があります。それが説明するすべてのものは、ApplicativeとMonads(およびとりわけArrows)の間で類似しているものであることがわかります。それが言っていないことは、正確にはそれらの間の小さいが有用な区別です。
-E
ここでの答えはモノイドとモナドの両方を定義するのに優れた仕事をしますが、それでも質問に答えるようには見えません:
そして、それほど重要ではないメモで、これは本当ですか?もしそうなら、説明を提供できますか(願わくば、Haskellの経験があまりない人でも理解できると思います)。
ここで欠落している問題の核心は、「モノイド」の別の概念、より正確にはいわゆるカテゴリー化、つまりモノイドカテゴリのモノイドの概念です。悲しいことに、Mac Laneの本自体が非常に混乱しています:
X
結局のところ、モナドはの内部関数のカテゴリのモノイドにすぎず、X
製品×
は内部関数の構成と単位関数によって識別されるユニットに置き換えられています。
なぜこれが混乱するのですか?それはの「内部ファンクターのカテゴリーのモノイド」が何であるかを定義していないからX
です。代わりに、この文は、すべての内部ファンクターのセット内のモノイドを、ファンクター構成をバイナリ演算として、アイデンティティファンクターをモノイド単位として取得することを示唆しています。これは完全に正常に機能し、アイデンティティファンクターを含み、ファンクター構成で閉じられているエンドファンクターのサブセットのモノイドに変わります。
しかし、これは正しい解釈ではありません。この段階では、この本では明確にされていません。モナドf
は固定された内部ファンクターであり、構成の下で閉じられた内部ファンクターのサブセットではありません。一般的な構成は、使用するf
ために生成するすべてのセット取ることによってモノイドをk
倍組成物f^k = f(f(...))
をf
含む、それ自体と、k=0
アイデンティティへのその対応しますf^0 = id
。そして今、S
これらすべてのすべての力のセットk>=0
は、確かに「製品×内部ファンクターの構成とアイデンティティ内部ファンクターによるユニットセットで置き換えられた」モノイドです。
それでも:
S
は、任意のファンクタに対して、f
または文字通りの任意の自己マップに対しても定義できますX
。によって生成されるモノイドf
です。S
ファンクタ構成と恒等ファンクタによって与えられるモノイド構造はf
、モナドであるかどうかとは関係ありません。さらに、混乱を招くために、「モノイドカテゴリのモノイド」の定義は、目次からわかるように、本の後半にあります。そして、この概念を理解することは、モナドとの関係を理解するために絶対的に重要です。
(モナドに後の章VIより付属)モノイド章VIIに行く、我々は、いわゆるの定義見つける厳密モノイド圏トリプルなどを(B, *, e)
、ここでB
カテゴリ、あるbifunctor固定他の成分と各成分に対する(ファンクタは)はの単位オブジェクトであり、結合性と単位法則を満たしています。*: B x B-> B
e
B
(a * b) * c = a * (b * c)
a * e = e * a = a
任意のオブジェクトのためa,b,c
のB
、および任意の射で同じアイデンティティa,b,c
を持つe
に置き換えid_e
のアイデンティティ射、e
。関心のある私たちの場合、射としての自然変換B
をX
伴うの内部*
ファンクター、ファンクター組成およびe
恒等ファンクターがどこにあるかを観察することは、直接検証できるように、これらのすべての法則が満たされていることを観察することは現在有益です。
この本の後に続くのは、「緩和された」モノイドカテゴリの定義です。この法則では、いわゆるコヒーレンス関係を満たすいくつかの固定された自然変換のみを法として保持しますが、エンドファンクターカテゴリの場合は重要ではありません。
最後に、第VII章のセクション3「モノイド」では、実際の定義が示されています。
c
モノイドカテゴリのモノイドは、2つの矢印(射)(B, *, e)
をB
持つオブジェクトです。
mu: c * c -> c
nu: e -> c
3つの図を可換にします。我々の場合には、これらは正確に対応する自然変換されendofunctorsのカテゴリで射、あることを思い出してくださいjoin
とreturn
モナドのため。私たちは、組成物は作るとき、接続がより明確になっ*
置き換える、より明確c * c
でc^2
、どこc
私たちのモナドです。
最後に、3つの可換図(モノイドカテゴリのモノイドの定義)は一般的な(厳密でない)モノイドカテゴリに対して記述されていますが、私たちの場合、モノイドカテゴリの一部として発生するすべての自然変換は実際には同一です。これにより、図はモナドの定義の図とまったく同じになり、対応が完全になります。
要約すると、どのモナドも定義上は内部関数であり、したがって内部関数のカテゴリのオブジェクトです。モナドjoin
とreturn
演算子は、その特定の(厳密な)モノイドカテゴリのモノイドの定義を満たします。逆に、エンドファンクターのモノイドカテゴリのモノイドは、定義上(c, mu, nu)
、オブジェクトと2つの矢印で構成されるトリプルです。たとえば、この場合は自然変形であり、モナドと同じ法則を満たします。
最後に、(古典的な)モノイドとモノイドカテゴリのより一般的なモノイドの主な違いに注意してください。2つの矢印mu
とnu
上記もうバイナリ操作とセット単位ではありません。代わりに、1つの固定エンドファンクターがありc
ます。本の*
混乱した発言にもかかわらず、ファンクタの構成とアイデンティティファンクタだけでは、モナドに必要な完全な構造を提供しません。
別のアプローチは、標準的なモノイドと比較することであろうC
セットの全ての自己マップのA
バイナリ操作は、標準デカルト積をマッピングするために見ることができる組成物である、C x C
にC
。分類されたモノイドに渡すと、デカルト積x
をファンクタ構成*
で置き換えます。バイナリ演算は、演算子のコレクションであるto mu
から
c * c
への自然変換に置き換えられますc
join
join: c(c(T))->c(T)
すべてのオブジェクトT
(プログラミングのタイプ)。そして、古典的なモノイドの恒等要素は、固定された1点セットからのマップの画像で識別でき、return
演算子のコレクションで置き換えられます
return: T->c(T)
しかし、現在、デカルト積はなくなっているため、要素のペア、したがって二項演算はありません。