誰が最初に次のように言いましたか?
モナドはエンドファンクターのカテゴリの中では単なるモノイドです、問題は何ですか?
そして、それほど重要ではないが、これは本当であり、もしそうなら、あなたは説明をすることができるだろう(うまくいけば、Haskellの経験があまりない人が理解できるもの)。
その特別な言い回しは、James Iryによるもので、彼は非常におもしろいプログラミング言語の簡潔で不完全かつほとんど間違った歴史で、架空のものと考えています。フィリップワドラーに。
オリジナルの引用はSaunders Mac Laneからのもので、カテゴリ理論の基本的なテキストの1つであるWorking Mathematician用のカテゴリです。 ここでは という文脈にありますが、これはおそらくそれが何を意味するのかを正確に学ぶための最良の場所です。
しかし、私は刺します。元の文はこれです:
つまり、XのモナドはXの内部関数の単なる単体であり、積×は内部関数の単位に置き換えられ、単位は単位固有関数によって設定されます。
Xこちらがカテゴリです。内部関数は、あるカテゴリからそれ自体へのファンクタです(関数型プログラマに関する限り、通常はallFunctor
sですが、それらはほとんど1つのカテゴリだけを扱います;型のカテゴリですが - 私はdigress)しかし、あなたは「Xのendofunctors」のカテゴリーである別のカテゴリーを想像することができます。これは、対象が内部助言者であり、射が自然変換であるカテゴリです。
そしてそれらの内部関数のうち、それらのいくつかはモナドかもしれません。モナドはどれですか?特定の意味で正確にモノイドのもの。モナドからモノイドへの正確なマッピングを詳しく説明するのではなく(Mac Laneが望んでいたよりはるかに良いので)、それぞれの定義を並べて比較してみましょう。
Functor
インスタンスを持つ種類* -> *
の型コンストラクター)join
として知られています。)return
Haskellで)ちょっと一言で言えば、これらの定義は両方とも同じ 抽象概念 のインスタンスであることがわかります。
直感的に、私は空想数学の語彙が言っているのはそれだと思う:
モノイドはオブジェクトの集合とそれらを組み合わせる方法です。よく知られているモノイドは次のとおりです。
もっと複雑な例もあります。
さらに、すべてのモノイドは同一性を持ちます。他のものと組み合わせると効果があります。
最後に、モノイドは結合的でなければなりません。 (オブジェクトの左から右への順序を変更しない限り、必要に応じて組み合わせの長い文字列を減らすことができます)加算はOKです((5 + 3)+1 == 5+(3+ 1))、しかし減算は((5-3)-1!= 5-(3-1))ではありません。
それでは、特別な種類のセットと、オブジェクトを組み合わせる特別な方法を考えてみましょう。
あなたのセットが特別な種類のオブジェクトを含んでいるとしましょう:関数。そしてこれらの関数は興味深いシグネチャを持っています:それらは数字に数字を、文字列に文字列を運んでいません。代わりに、各関数は2段階のプロセスで番号のリストに番号を伝えます。
例:
また、私たちの関数の組み合わせ方は特別です。関数を組み合わせる簡単な方法は、合成です。上の例を見て、それぞれの関数を自分自身で構成しましょう。
型理論に深く入り込むことなしに、整数を得るために2つの整数を組み合わせることができるということですが、2つの関数を構成して同じ型の関数を得ることはできません。 (型a - > aを持つ関数は作成されますが、a-> [a]獲得 ' t。)
それでは、関数を組み合わせる別の方法を定義しましょう。これら2つの関数を組み合わせるとき、結果を "二重に折り返す"ことはしたくありません。
これが私たちのやり方です。 2つの関数FとGを結合したい場合は、次のプロセスに従います(bindingと呼ばれます)。
私たちの例に戻って、この新しい "束縛"関数を使って関数をそれ自身と結合(束縛)しましょう:
関数を結合するためのこのより洗練された方法は連想的です(派手なラッピングをしていないときの関数構成が連想的である方法からわかるように)。
すべて一緒に結び付ける
結果を「ラップ」する方法はたくさんあります。リストやセットを作成したり、結果がないかどうかを確認しながら最初の結果以外のすべてを破棄したり、サイドカーの状態を添付したり、ログメッセージを印刷したりすることができます。
本質的なアイディアを直感的に理解してもらうために、定義を少し緩めてみました。
私たちのモナドはa - > [a]型の関数で動作すると主張することで物事を少し単純化しました。実際、モナドはa - > m bの型の関数に作用しますが、一般化は一種の技術的詳細であり、主な洞察ではありません。
まず、使用する拡張機能とライブラリ:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
これらのうち、RankNTypes
は、以下に絶対に不可欠な唯一のものです。 私はかつてRankNTypes
の説明を書いたが、一部の人々は有用だと思うようだ なので、それを参照する。
引用 Tom Crockettの優れた答え 、
モナドは...
- エンドファンクター、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つの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つは次のとおりです。
さて、これで邪魔にならないように、定義の条項に取り組みましょう。
最初の節は「エンドファンクター、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
Compose
とIdentity
は両方とも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.
この投稿には、Mac Laneの悪名高い引用の推論をよりよく理解するために来ました作業数学者のカテゴリー理論。
何かを説明する際に、そうでないものを説明することもしばしば有用です。
Mac Laneがモナドを記述するために記述を使用しているという事実は、モナドに固有の何かを記述することを暗示しているかもしれません。我慢してください。声明をより広く理解するには、彼がnotモナド特有の何かを記述していることを明確にする必要があると思います。このステートメントは、特にApplicativeとArrowsを説明しています。同じ理由で、Intに2つのモノイド(SumとProduct)があり、endofunctorのカテゴリのXにいくつかのモノイドがあります。しかし、類似点にはさらに多くのものがあります。
MonadとApplicativeの両方が基準を満たしています:
(例:毎日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
になっています。
したがって、ステートメントは次のようにまとめられます。
:: f a -> g b
(つまり、パラメーター化された型からパラメーター化された型へ):: f a -> f b
(つまり、1つのパラメーター化された型から同じパラメーター化された型へ)...それで、この構造の力はどこにあるのでしょうか?完全なダイナミクスを理解するために、モノイド(アイデンティティ矢印:: single object -> single object
のように見える単一のオブジェクト)の典型的な描画では、anyでパラメーター化された矢印の使用が許可されていることを説明できません。数モノイドの値、oneモノイドで許可されたタイプオブジェクトから。エンド、同等性の〜アイデンティティ矢印の定義ignoresファンクタのtype valueおよび最も内側の「ペイロード」層のタイプと値の両方。したがって、関数型が一致するすべての状況で等価はtrue
を返します(たとえば、Nothing -> Just * -> Nothing
はJust * -> 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
);静的fold
はペイロードについて何も語らない)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 -> b
対k :: a -> m b
)および計算の同じ戻り値型を持つパラメーターの位置(つまり、それぞれa -> b -> b
対b -> a -> b
)でなかった場合、パラメーター化した可能性があります両方の定義で再利用するためのモノイド論理、テンソル積。要点を確認するための演習として、~t
を試して実装すると、(<*>)
の定義方法に応じて(>>=)
とforall a b
になります。
私の最後のポイントが概念的に少なくとも真である場合、ApplicativeとMonadの間の正確で唯一の計算上の違い、つまりパラメーター化する関数について説明します。つまり、これらの型クラスの実装との違いはexternalです。
結論として、私自身の経験では、Mac Laneの悪名高い引用は、偉大な "goto"ミームを提供しました。これは、Haskellで使用されるイディオムをよりよく理解するためにCategoryをナビゲートしながら参照するための指針です。 Haskellで見事にアクセスできる強力なコンピューティング能力の範囲を獲得することに成功しました。
ただし、モナド以外でのステートメントの適用可能性を最初に誤解した方法と、ここで伝えたいことには皮肉があります。それが記述するすべては、ApplicativeとMonads(およびとりわけArrows)の間で類似しているものであることが判明しました。それが言っていないのは、正確には小さいながらも有用な違いです。
-E
注:いいえ、これは正しくありません。ある時点で、Dan Piponi自身からのこの回答に対するコメントがあり、ここでの原因と結果は正反対であり、James Iryの質問に答えて彼の記事を書いたということです。しかし、それはおそらく強制的な片付けによって、取り除かれたようです。
以下が私の最初の答えです。
HaskellでDan Piponi(sigfpe)がモノイドからモナドを派生させた記事で、Iryが モノイドからモナドへ を読んだことは十分にあり得ます。カテゴリ理論と " Hask 上の内部関数のカテゴリ"の明示的な言及。いずれにせよ、モナドがエンドファンクターのカテゴリのモノイドであることが何を意味するのか疑問に思う人は誰でも、この派生を読むことから利益を得るかもしれません。