無料のモナドとは何ですか?


368

私が見てきた用語無料モナドがポップアップし、すべての 、その後いくつかの時間のために、誰もがちょうど彼らが何であるかの説明を与えることなく、それらを議論/使用するようです。だから:無料のモナドは何ですか?(私はモナドとHaskellの基本に精通していると思いますが、カテゴリー理論については非常に大まかな知識しか持っていません。)


12
かなり良い説明はここにあるhaskellforall.com/2012/06/...
ロジャーLindsjö

19
@Rogerそれは私をここに連れてきたページのようなものです。私にとって、この例では、「Free」という名前の型のモナドインスタンスを定義しています。
David

回答:


295

エドワード・クメットの答えは明らかに素晴らしいです。しかし、それは少し技術的です。これはおそらくよりアクセスしやすい説明です。

無料のモナドは、ファンクタをモナドに変換する一般的な方法にすぎません。つまり、与えられたファンクタf Free fはモナドです。これは、2つの関数を取得する場合を除いて、あまり役に立ちません。

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

これらの1つ目ではモナドに「入る」ことができ、2つ目ではモナドから「出る」ことができます。

より一般的には、Xが追加のPを含むYである場合、「フリーX」は、何も追加することなくYからXに移動する方法です。

例:モノイド(X)は追加の構造(P)を持つセット(Y)であり、基本的には操作(加算のことを考えることができます)と特定のアイデンティティ(ゼロなど)があると述べています。

そう

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

今、私たちは皆リストを知っています

data [a] = [] | a : [a]

まあ、tそれ[t]がモノイドであることがわかっているタイプを考えると

instance Monoid [t] where
  mempty   = []
  mappend = (++)

そのため、リストはセット(またはHaskellタイプ)に対する「無料のモノイド」です。

さて、無料のモナドは同じ考えです。ファンクタを取り、モナドを返します。実際、モナドはエンドファンクターのカテゴリーではモノイドと見なすことができるため、リストの定義は

data [a] = [] | a : [a]

無料モナドの定義によく似ています

data Free f a = Pure a | Roll (f (Free f a))

そしてMonadインスタンスはに類似性があるMonoidリストのインスタンスを

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

今、私たちは2つの操作を取得します

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)

12
これは、私が今まで見た「無料」の最も親しみやすい説明かもしれません。特に「より一般的に」で始まる段落。
John L

16
Free f a = Pure a | Roll (f (Free f a))として見るのは興味深いと思いますFree f a = a + fa + ffa + ...。つまり、「fは何度も適用されます」。次にconcatFree(つまりjoin)「fを任意の回数適用して(fを任意の回数適用してa)」とし、ネストされた2つのアプリケーションを1つに折りたたみます。そして>>=、「fをaに何度も適用する」と「どのようにaから(bをfを何回も適用する)に取得する」という方法で、基本的に後者を前者のaの内部に適用し、ネストを折りたたみます。今私はそれを手に入れました!
jkff 2013年

1
あるconcatFree基本的にはjoin
rgrinberg 2014

11
「これはおそらくもっとわかりやすい説明です。[…]実際、モナドは内部ファンクタのカテゴリではモノイドと見なすことができるため、…」それでも、これは非常に良い答えだと思います。
Ruud

2
「モナドはエンドファンクタのカテゴリのモノイドとして見ることができる」<3(すべてのhaskellerがその参照について知っている必要があるため、stackoverflow.com / a / 3870310/1306877にリンクする必要があります!)
tlo

418

これはさらに簡単な答えです。モナドは、モナドのコンテキストが折りたたまれたときに「計算」されるものですjoin :: m (m a) -> m a>>=として定義できることを思い出してくださいx >>= y = join (fmap y x))。これは、モナドが一連の計算の連鎖を通じてコン​​テキストを運ぶ方法です。シリーズの各ポイントで、前の呼び出しのコンテキストが次の呼び出しで折りたたまれているためです。

無料のモナドを満たすすべてのモナドの法律が、任意の崩壊(すなわち、計算)を行いません。ネストされた一連のコンテキストを構築するだけです。そのような自由なモナド値を作成するユーザーは、それらのネストされたコンテキストで何かを行う責任があります。そのため、そのような合成の意味は、モナド値が作成されるまで延期できます。


8
あなたの段落はフィリップの投稿に本当に素晴らしい追加をします。
デビッド

20
私はこの答えが本当に好きです。
danidiaz 2013年

5
無料のモナドはモナド型クラスを置き換えることができますか?つまり、無料のモナドのリターンとバインドのみを使用してプログラムを記述し、MwybeまたはListなどを使用して結果を結合したり、バインド/連結された関数呼び出しの1つのシーケンスの複数のモナドビューを生成したりできます。つまり、ボトムと非終端を無視します。
ミスタービー2014年

2
この回答は役立ったが、私はそれが私が私が会っていなかった混乱していると思いますNICTAにもちろん「参加」と読みhaskellforall.com/2012/06/...を。ですから、私にとって理解の秘訣は、理解するまでたくさんの答えを読むことです(NICTAリファレンス:github.com/NICTA/course/blob/master/src/Course/Bind.hs
Martin Capodici

1
この答えはこれまでで最高です
Curycu

142

フリーfooは、偶然にもすべての「foo」法則を満たす最も単純なものです。つまり、それはfooになるために必要な法則を正確に満たし、余分なものは何もないということです。

忘れっぽいファンクタは、あるカテゴリから別のカテゴリに移動するときに構造の一部を「忘れる」ものです。

ファンクタを考えるF : D -> CG : C -> D、私たちが言うF -| GFに左随伴であるG、またはG右随伴あるFたびforallは、B:F a -> bと同型であるa -> G b矢印が適切なカテゴリどこから来ました、。

正式には、無料のファンクターは忘れっぽいファンクターに随伴している。

無料モノ​​イド

簡単な例であるフリーモノイドから始めましょう。

いくつかのキャリアセットTで定義されたモノイド、2つの要素を1つにまとめる2項関数f :: T → T → T、およびunit :: Tを使用して、連想法則と恒等則を作成しますf(unit,x) = x = f(x,unit)

あなたはファンクタを作ることができますU(彼らはマップを確認し、ある矢印がモノイド準同型あり、モノイドのカテゴリからunitunitカテゴリーに他のモノイドに、そしてあなたが意味を変えずに、他のモノイドへのマッピングの前または後に構成できること)セット(矢印は単なる関数の矢印です)は、操作とを「忘れて」unit、キャリアセットを提供します。

次に、Fセットのカテゴリから、このファンクタに隣接したままになっているモノイドのカテゴリにファンクタを定義できます。そのファンクタはセットにマッピングファンクタでaモノイドに[a]unit = []mappend = (++)

これまでの例を確認するには、疑似ハスケルで:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

次に、F無料で表示するにはU、忘れっぽいファンクタに隣接していることを示す必要があります。つまり、前述のように、

F a → b に同型である a → U b

ここで、のターゲットがモノイドFのカテゴリにあることを思い出してくださいMon。矢印はモノイド準同型であるため、のモノイド同型[a] → bがの関数によって正確に記述できることを示す必要がありますa → bます。

Haskellでは、我々はに住んでいるこの側面を呼び出すSet(ER、Haskちょうど、私たちはふりをすることをHaskellの種類のカテゴリが設定されている)foldMapから専門ときに、Data.FoldableリストにはタイプがありますMonoid m => (a → m) → [a] → m

これが付随的であることから続く結果があります。特に、忘れてしまった場合は無料で作成し、次に忘れた場合は、一度忘れた場合と同じように、これを使用してモナディック結合を作成できます。以来UFUFU(FUF)UF、と私たちはから身元モノイド準同型に渡すことができます[a][a]、私たちのadjunctionを定義同型てからリスト同型があることを得る[a] → [a]タイプの関数であり、a -> [a]、これはリストのためだけのリターンです。

これらの用語のリストを次のように記述することで、これらすべてをより直接的に作成できます。

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

無料モナド

だから無料モナドとはですか?

さて、私たちは前と同じことをします。矢印がモナド同型であるモナドのカテゴリから、矢印が自然な変換である内部関数のカテゴリまで、忘れっぽいファンクタUから始めます。それに。

では、これは通常使用されるフリーモナドの概念とどのように関連していますか?

何かがフリーのモナドであることを知っていると、Free fからモナド準同型を与えることFree f -> mは、から自然変換(ファンクタ同型)を与えることと同じ(同型)であることがわかりf -> mます。FをUに隣接さF a -> bせるにはa -> U b、同型でなければならないことに注意してください。ここでは、モナドをファンクタにマッピングしています。

Fは、ハッキングでパッケージでFree使用するタイプと少なくとも同型freeです。

また、次のように定義することで、フリーリスト用の上記のコードにより厳密に類似した方法で構築できます。

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Cofree Comonads

忘れっぽいファンクタが存在すると仮定して、それに隣接するものを調べることで、同様の何かを構築できます。cofreeファンクタは、単純に忘れっぽいファンクタの/ right adjoint /であり、対称性によって、何かがcofreeコモナドであることを知ることw -> Cofree fは、からコナモ準同型を与えることがからの自然な変換を与えることと同じことであることを知ることと同じですw -> f


12
@PauloScardine、これはあなた心配する必要があるものではありません。私の質問は、いくつかの高度なデータ構造を理解し、たぶん今のところHaskell開発の最先端を垣間見ることに興味を示したからです。これは、Haskellを実際に作成することがこれまでに何を必要としているか、それを表すものではありません。(そして前向きに、IOラーニングステージを再び通過すると、それはより良くなります)
David

8
@PauloScardine無料のモナドがあっても、Haskellで生産的にプログラミングするために上記の答えは必要ありません。事実、私はこの方法で無料のモナドを攻撃することを、カテゴリー理論のバックグラウンドを持っていなかった誰かに勧めることはお勧めしません。運用の観点からそれについて話し、カテゴリー理論に飛び込むことなくそれを使用する方法を理解する多くの方法があります。しかし、理論に踏み込まずにどこから来たのかという質問に答えることは不可能です。無料の構築はカテゴリー理論の強力なツールですが、それらを使用するためにこの背景は必要ありません。
エドワードKMETT

18
@PauloScardine:Haskellを効果的に利用し、自分が何をしているかを理解するための計算はまったく必要ありません。楽しさと利益のために使うことができる余分な良さである場合、「この言語は無愛想です」と文句を言うのは少し奇妙です。これらのことは、ほとんどの命令型言語では得られません。エクストラについて不満を言うのはなぜですか?数学的に推論しないことを選択し、他の新しい言語と同じようにアプローチすることができます。
サラ、

3
@サラ:コンピュータ理論とラムダ計算サームに重くはないhaskellに関するドキュメントやIRCの会話はまだ見ていません。
Paulo Scardine、2013年

11
@PauloScardineこれは少しOTを漂わせていますが、Haskellの弁護では、同様の技術的な事柄が他のすべてのプログラミング言語に当てはまります。Xがモナドである理由/方法は多くの人にとって興味深いものですが、IEEE浮動小数点標準に関する議論は興味深いものではありません。結果はそのまま使用できるため、どちらの場合もほとんどの人にとって重要ではありません。
David

72

フリーモナド(データ構造)は、モナド(クラス)に対するリスト(データ構造)と同様にモナド(クラス)に対するものです。これは、コンテンツをどのように組み合わせるかを後で決定できる簡単な実装です。


あなたはおそらくモナドが何であるか、そして各モナドがfmap+ join+ returnまたはbind+の特定の(モナド法則に従う)実装を必要とすることを知っているでしょうreturn

Functor(の実装fmap)があると仮定しますが、残りは実行時に行われた値と選択に依存します。つまり、モナドプロパティを使用できるようにしたいが、後でモナド関数を選択したいということです。

これは、Free Monad(データ構造)を使用して行うことができます。FreeMonad(データ構造)は、Functor(タイプ)をjoin、リダクションというよりはむしろそれらのFunctorのスタックとなるようにラップします。

本物returnjoinあなたが使用したいが、今リダクション機能へのパラメータとして指定することができますfoldFree

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

タイプを説明するためにFunctor fMonad mbで置き換えることができます(m a)

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

8
この回答は、それらが何のために役立つかさえ理解しているという印象を与えました。
David

59

ハスケルフリーモナドはファンクタのリストです。比較:

data List a   = Nil    | Cons  a (List a  )

data Free f r = Pure r | Free (f (Free f r))

Pureに類似しNilFreeに類似していますCons。無料のモナドは、値のリストではなくファンクタのリストを格納します。技術的には、別のデータ型を使用して無料のモナドを実装できますが、どの実装も上記のものと同型である必要があります。

抽象構文木が必要なときはいつでも無料のモナドを使います。無料モナドの基本関数は、構文ツリーの各ステップの形状です。

誰かがすでにリンクしている私の投稿は、無料のモナドで抽象的な構文ツリーを構築する方法のいくつかの例を示しています


6
私はあなたが定義をするのではなく単にアナロジーを描いていたのを知っていますが、フリーのモナドは確かにファンクターのリストに決して似ていません。それはファンクターのツリーにはるかに近いです。
Tom Ellis、

6
私は自分の専門用語を支持します。たとえば、私のインデックスコアパッケージを使用すると、値の代わりにファンクタをバインドすることを除いて、リストモナドと同じように動作する「フリーモナド内包表記」を定義できます。無料モナドとは、すべてのHaskellの概念をファンクタのカテゴリに変換すると、リストがフリーモナドになるという意味で、ファンクタのリストです。ファンクタの真のツリーは、完全に別のものになります。
Gabriel Gonzalez

4
モナドは、ある意味ではモノイドの概念の分類であるため、自由モナドは自由モノイド、つまりリストに類似しています。その点であなたは確かに正しいです。ただし、フリーモナドの値の構造はリストではありません。これは、前述したように、木で以下I詳細
トム・エリス

2
@TomEllis技術的には、ベースファンクタが製品ファンクタである場合のみツリーになります。サムファンクタをベースファンクタとして使用する場合、スタックファンクタはスタックマシンにより似ています。
Gabriel Gonzalez

21

簡単な具体例が役立つと思います。ファンクタがあるとします

data F a = One a | Two a a | Two' a a | Three Int a a a

明らかでfmap。次いでFree F a葉タイプ有する木の種類であるaノードでタグ付けされているがOneTwoTwo'ThreeOne-nodesには1つの子があり、Two-およびTwo'-nodesには2つの子があり、Three-nodesには3 つの子があり、さらにでタグ付けされていIntます。

Free Fモナドです。 値を持つ単なる葉であるツリーにreturnマップxしますxt >>= fそれぞれの葉を見て、それらを木に置き換えます。葉に価値がある場合、yその葉を木に置き換えますf y

ダイアグラムでこれはより明確になりますが、簡単に描くための機能がありません!


14
皆さんが言っているのは、無料のモナドがファンクタ自体の形を取っているということです。つまり、ファンクタがツリー状(製品)であれば、フリーモナドはツリー状です。リストのような(合計)場合、フリーモナドはリストのようなものです。関数のようであれば、フリーモナドは関数のようです。これは私には理にかなっています。したがって、無料のモノイドのように、mappendのすべてのアプリケーションを完全に新しい要素を作成するものとして扱い続けます。無料のモナドでは、ファンクターのすべてのアプリケーションを完全に新しい要素として扱います。
Bartosz Milewski 2013

4
ファンクタが「合計ファンクタ」であっても、結果のフリーモナドは依然としてツリーのようなものです。結局、ツリー内に複数のタイプのノード(合計の各コンポーネントに1つ)ができます。「合計ファンクター」がX-> 1 + Xの場合、実際にはリストを取得しますが、これは単にツリーの縮退した種類です。
トム・エリス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.