なぜモナドが必要なのですか?


366

私の控えめな意見では、「モナドとは何ですか?」という有名な質問に対する答えです。、特に最も投票されたものは、モナドが本当に必要な理由を明確に説明せずに、モナドとは何かを説明しようとします。問題の解決策として説明できますか?




4
あなたはすでにどんな研究をしましたか?どこを見た?どのようなリソースを見つけましたか? 質問する前にかなりの量の調査を行っていただき、どのような調査を行ったかの質問に答えてください。リソースの動機を説明しようとするリソースはたくさんあります。まったく見つからない場合は、もう少し調査する必要があるかもしれません。見つかったものの、役に立たなかった場合、何が見つかったか、具体的になぜうまくいかなかったのかを説明すると、これはより良い質問になります。
DW

8
これは間違いなくProgrammers.StackExchangeに適していますが、StackOverflowには適していません。できれば移行するよう投票しますが、できません。=(
jpmc26 2015年

3
@ jpmc26おそらく「主に意見に基づく」ものとしてそこで閉じられるでしょう。ここでは、少なくともチャンスがあります(膨大な数の賛成投票、昨日の迅速な再開、およびまだ近い投票はありません)
イズカタ2015年

回答:


580

なぜモナドが必要なのですか?

  1. 関数だけでプログラミングしたい。(結局のところ「関数型プログラミング(FP)」)。
  2. 次に、最初の大きな問題があります。これはプログラムです:

    f(x) = 2 * x

    g(x,y) = x / y

    最初に何を実行するかをどのように言うことができ ますか?関数だけを使用して、順序付けられた一連の関数(つまりプログラムどのように形成できますか?

    解決策:関数の作成。最初に必要な場合gf、次のように書いてくださいf(g(x,y))。このように、「プログラム」も関数ですmain = f(g(x,y))。OK、でも...

  3. その他の問題:一部の関数は失敗する可能性があります(つまりg(2,0)、0で除算)。私たちは持っていないない「例外」 FP内を(例外は関数ではありません)。どうすれば解決できますか?

    解決策:関数が2つの種類のものを返すことを許可しましょう:g : Real,Real -> Real(2つの実数から1つの実数への関数)を持つ代わりに、(g : Real,Real -> Real | Nothing2つの実数から(実数または何もない)への関数)を許可しましょう。

  4. しかし、関数は(より簡単に)1つのものだけを返す必要があります。

    解決策:返される新しいタイプのデータを作成してみましょう。「ボクシングタイプ」は、実在するものか、まったく存在しないものを囲みます。したがって、私たちはを持つことができますg : Real,Real -> Maybe Real。OK、でも...

  5. 今はどうなりf(g(x,y))ますか?fを使用する準備ができていませんMaybe Real。そして、私たちはと接続することができ、すべての機能を変更したくないg消費するようにMaybe Real

    解決策:関数を「接続」/「作成」/「リンク」する特別な関数を用意しましょう。このようにして、背後で1つの関数の出力を調整して、次の関数にフィードできます。

    私たちの場合: g >>= f(connect / compose gto f)。我々はしたい>>=得るためにg、それがされた場合には、それを検査し、S」の出力をNothing単に呼び出すことはありませんfリターンとNothing。または逆に、箱に入っているものを取り出し、それと一緒にRealfをやります。(このアルゴリズムは>>=Maybe型のの実装にすぎません)。また、「ボックス化タイプ」(異なるボックス、異なる適応アルゴリズム)ごとに1 回だけ>>=記述する必要があることに注意してください。

  6. 同じパターンを使用して解決できる他の多くの問題が発生します。1.「ボックス」を使用してさまざまな意味/値をコード化/保存し、gそれらの「ボックス化された値」を返すような関数を使用します。2. の出力をの入力にg >>= f接続するのに役立つコンポーザー/リンカーがあるため、何も変更する必要はありません。gff

  7. この手法を使用して解決できる注目すべき問題は次のとおりです。

    • 関数のシーケンス内のすべての関数(「プログラム」)が共有できるグローバルな状態:ソリューションStateMonad

    • 「純粋でない関数」:同じ入力に対して異なる出力を生成する関数は好きではありません。したがって、これらの関数にマークを付けて、タグ付き/ボックス化された値を返すようにします:モナド。IO

完全な幸せ!


64
@Carl私たちを啓発するためのより良い答えを書いてください
XrXr

15
@Carlこのパターンから恩恵を受ける多くの問題があり(ポイント6)、IOモナドがリストのもう1つの問題にすぎないIO(ポイント7)ことは答えで明らかだと思います。一方IO、最後と最後にしか表示されないので、「ほとんどの場合、IOについて話している...」とは理解しないでください。
cibercitizen1 2015年

4
モナドについての大きな誤解:状態についてのモナド。例外処理に関するモナド。モナドなしで純粋なFPLにIOを実装する方法はありません。モナドは明確です(矛盾はですEither)。答えのほとんどは「なぜファンクタが必要なのか」についてです。
vlastachu 2015年

4
「6. 2. の出力をの入力にg >>= f接続するのに役立つコンポーザー/リンカーがあるので、何も変更する必要はありません。」gff これはまったく正しくありません。前、中f(g(x,y))f何かを作り出すことができます。可能性がありますf:: Real -> String。「モナディック構成」では、生成するように変更する必要があります。変更ないとMaybe String、タイプが適合しません。また、>>=それ自体が合わない !! それ>=>はこの構成を行うのではなく、>>=です。カールの答えの下でdfeuerとの議論を参照してください。
Will Ness

3
あなたの答えは、モナドIMOが実際に「関数」の構成/性質(Kleisli矢印)であると最もよく説明されているという意味で正しいですが、どのタイプがどこに行くかについての正確な詳細は、それらを「モナド」にするものです。箱はあらゆる種類の方法(Functorなど)で配線できます。それらを一緒に配線するこの特定の方法は、「モナド」を定義するものです。
Will Ness

219

答えは、もちろん「私たちはしません」です。すべての抽象化と同様に、それは必要ではありません。

Haskellはモナドの抽象化を必要としません。純粋な言語でIOを実行する必要はありません。IOタイプは、それ自体でそのちょうど良いの世話をします。既存のモナド脱糖doブロックは、脱糖に置き換えることができるbindIOreturnIOfailIOのように定義されているGHC.Baseモジュール。(ハッキングに関する文書化されたモジュールではないため、文書化のためにそのソースをポイントする必要があります。)したがって、モナド抽象化の必要はありません。

それで、それが必要ない場合、なぜそれが存在するのですか?なぜなら、多くの計算パターンがモナド構造を形成していることがわかったからです。構造の抽象化により、その構造のすべてのインスタンスで機能するコードを記述できます。より簡潔に言えば、コードの再利用です。

関数型言語では、コードを再利用するための最も強力なツールは関数の合成です。古き良き(.) :: (b -> c) -> (a -> b) -> (a -> c)オペレーターは非常に強力です。小さな関数を簡単に作成し、最小限の構文または意味上のオーバーヘッドでそれらを結合することが容易になります。

しかし、型が正しく機能しない場合があります。あなたが持っているとき、あなたは何をしますかfoo :: (b -> Maybe c)bar :: (a -> Maybe b)?とは同じ型ではないfoo . barためb、型チェックを行いませんMaybe b

しかし...それはほぼ正しい。ちょっとした余裕が欲しいだけです。Maybe b基本的にあたかも扱えるようにしたいb。ただし、これらを同じタイプとして完全に扱うことはお勧めできません。これは、Tony Hoareが有名な10億ドルの間違いと言っていたヌルポインターとほぼ同じです。したがって、それらを同じタイプとして処理できない場合は、合成メカニズムが(.)提供する拡張方法を見つけることができるでしょう。

その場合、の根底にある理論を実際に検討することが重要(.)です。幸いなことに、誰かがすでにこれを行ってくれました。カテゴリと呼ばれる数学的構造を組み合わせ(.)id形成することがわかりました。しかし、カテゴリーを形成する他の方法があります。たとえば、Kleisliカテゴリーでは、構成されているオブジェクトを少し増やすことができます。のクライスリカテゴリMaybeは、(.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)およびで構成されますid :: a -> Maybe a。つまり、カテゴリオーグメント内のオブジェクトである(->)Maybe、そう(a -> b)なりました(a -> Maybe b)

そして突然、従来の(.)操作では機能しないものに合成の力を拡大しました。これは新しい抽象化力の源です。Kleisliカテゴリーは、単なるタイプよりも多くのタイプで機能しMaybeます。それらは、カテゴリー法に従って、適切なカテゴリーを組み立てることができるすべてのタイプで機能します。

  1. 左のアイデンティティ:id . f=f
  2. 正しいアイデンティティ:f . id=f
  3. 関連性:f . (g . h)=(f . g) . h

型がこれらの3つの法則に従っていることを証明できる限り、それをクライスリカテゴリに変えることができます。そして、それの何が大事なのですか?さて、モナドはクライスリカテゴリとまったく同じであることがわかります。MonadさんはreturnKleisliと同じですidMonadさんは(>>=)Kleisliと同じではない(.)が、それは他の面でそれぞれを書くことは非常に簡単であることが判明しました。そして、カテゴリの法律では、違いを越え、それらを変換する際に、モナドの法則と同じである(>>=)(.)

では、なぜこのような面倒な作業をすべて行うのですか Monad言語に抽象化があるのはなぜですか?上記で触れたように、コードを再利用できます。2つの異なる次元でコードを再利用することもできます。

コードの再利用の最初の側面は、抽象化の存在から直接もたらされます。抽象化のすべてのインスタンスで機能するコードを記述できます。の任意のインスタンスで動作するループで構成されるモナドループパッケージ全体がありますMonad

二次元は間接的ですが、それは構成の存在から来ています。構成が簡単な場合は、コードを小さく再利用可能なチャンクで書くのが自然です。これは、(.)関数の演算子を使用することで、小さくて再利用可能な関数の作成を促進するのと同じです。

では、なぜ抽象化が存在するのでしょうか?これは、コードをさらに構成できるツールであることが証明されているため、再利用可能なコードが作成され、より再利用可能なコードの作成が促進されます。コードの再利用は、プログラミングの神聖なグライルの1つです。モナドの抽象化が存在するのは、それが私たちを少しその聖杯に向かって動かすからです。


2
カテゴリ全般とクライスリカテゴリの関係を説明できますか?あなたが説明する3つの法律はどのカテゴリーにも当てはまります。
dfeuer 2015年

1
@dfeuerああ。、コードでそれを置くためにnewtype Kleisli m a b = Kleisli (a -> m b)。クライスリカテゴリは、カテゴリ型の戻り値の型(bこの場合)が型コンストラクタの引数である関数mです。iff Kleisli mがカテゴリを形成する場合m、モナドです。
Carl

1
正確にカテゴリ型の戻り値型とは何ですか?Kleisli mそのオブジェクトから矢印というHaskellの種類と、このようなあるカテゴリーを形成すると思われるaのは、b関数からあるam bして、id = returnとし(.) = (<=<)。それは正しいですか、それとも私はさまざまなレベルのことを混同していますか?
dfeuer 2015年

1
@dfeuerそうです。オブジェクトはすべて型で、射は型aとの間にありますbが、単純な関数ではありません。それらはm、関数の戻り値の追加で装飾されています。
カール

1
カテゴリ理論の用語は本当に必要ですか?多分、Haskellは、型を画像の描画方法のDNAになるような型に変換し(依存型ですが*)、次にその画像を使用して、名前を小さなルビー文字でプログラムを作成すると、より簡単になるでしょう。アイコンの上。
aoeu256

24

ベンジャミンピアースはTAPL言った

型システムは、プログラム内の用語の実行時の動作に対する一種の静的近似を計算するものと見なすことができます。

そのため、強力な型システムを備えた言語は、型の不適切な言語よりも厳密に表現力があります。同じ方法でモナドについて考えることができます。

@Carlとsigfpeがポイントするように、モナド、タイプクラス、またはその他の抽象的なものに頼ることなく、必要なすべての操作をデータ型に装備できます。ただし、モナドを使用すると、再利用可能なコードを作成できるだけでなく、冗長な詳細をすべて抽象化することもできます。

例として、リストをフィルタリングしたいとします。最も簡単な方法は、に等しいfilter関数を使用することfilter (> 3) [1..10]です[4,5,6,7,8,9,10]

もう少し複雑なバージョンのfilterも、アキュムレータを左から右に渡します。

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

すべてを取得するにはi、そのようにi <= 10, sum [1..i] > 4, sum [1..i] < 25

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

と等しい[3,4,5,6]

またはnub、次の点でリストから重複した要素を削除する関数を再定義できますfilterAccum

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]等しい[1,2,4,5,3,8,9]。リストはアキュムレータとしてここに渡されます。リストモナドから離れることができるため、コードは機能します。したがって、計算全体が純粋なままになります(実際にnotElemは使用しません>>=が、使用できます)。ただし、IOモナドを安全に残すことはできません(つまり、IOアクションを実行して純粋な値を返すことはできません。値は常にIOモナドでラップされます)。もう1つの例は可変配列です。可変配列が存在するSTモナドを離れると、一定の時間内に配列を更新できなくなります。したがって、Control.Monadモジュールからのモナディックフィルタリングが必要です。

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterMリストのすべての要素に対してモナディックアクションを実行し、モナディックアクションが返す要素を生成しますTrue

配列を使用したフィルタリングの例:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

[1,2,4,5,3,8,9]期待どおりに印刷します。

そして、どの要素を返すかを尋ねるIOモナドを持つバージョン:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

例えば

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

そして最後の例として、filterAccumは次のように定義できますfilterM

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

StateT普通のデータ型である、フードの下で使用されるモナド、。

この例は、モナドによって計算コンテキストを抽象化し、@ Carlが説明するようにモナドの構成可能性によりクリーンな再利用可能なコードを作成できるだけでなく、ユーザー定義のデータ型と組み込みプリミティブを均一に扱うことを示しています。


1
この答えは、なぜモナド型クラスが必要なのかを説明しています。:私たちはモナドではなく、何か他のものを必要とする理由最良の方法は、理解するために、モナドと応用的ファンクタの違いについて読むことです12
user3237465 2015

20

IO特に優れたモナドと見なすべきではないと思いますが、初心者にとっては驚くべきモナドの1つであることは間違いないので、説明に使用します。

素朴なHaskellのIOシステムの構築

純粋に関数型の言語(実際にはHaskellが最初に使用した言語)で考えられる最も単純なIOシステムは次のとおりです。

main :: String -> String
main _ = "Hello World"

怠惰な場合、その単純な署名は、実際にはインタラクティブな端末プログラムを構築するのに十分ですが、非常に制限されます。最もイライラするのは、テキストしか出力できないことです。さらにエキサイティングな出力の可能性を追加した場合はどうなりますか?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

かわいいですが、もちろん、より現実的な「代替出力」はファイルへの書き込みです。ただし、ファイルから読み取る方法も必要です。万が一?

ええと、main₁プログラムを取得して単純にファイルをプロセスに(オペレーティングシステム機能を使用して)パイプするとき、基本的にファイル読み取りを実装しました。Haskell言語内からそのファイル読み取りをトリガーできるとしたら...

readFile :: Filepath -> (String -> [Output]) -> [Output]

これは、「インタラクティブプログラム」を使用String->[Output]して、ファイルから取得した文字列をフィードし、指定されたプログラムを単に実行する非インタラクティブプログラムを生成します。

ここに問題が1つあります。ファイルがいつ読み取られるかについては、私たちには本当に概念がありません。[Output]リストは確かに素敵な順序を与える出力が、我々はときにするため得ることはありません入力が行われますが。

解決策:行うべきことのリストの項目にもinput-eventsを作成します。

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

さて、不均衡に気づくかもしれません。ファイルを読み取って出力をそれに依存させることができますが、ファイルの内容を使用して、たとえば別のファイルを読み取ることもできません。明白な解決策:input-eventsの結果もIOだけでなく、タイプの何かにしOutputます。確かに単純なテキスト出力が含まれていますが、追加のファイルなどを読み取ることもできます。

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

これにより、実際にはプログラムで必要なファイル操作を表現できるようになります(ただし、パフォーマンスは良くないかもしれません)が、少し複雑すぎます。

  • main₃アクションのリスト全体を生成します。:: IO₁これを特別なケースとして持つ署名を単に使用しないのはなぜですか?

  • リストは、プログラムフローの信頼できる概要をもう提供していません。後続のほとんどの計算は、何らかの入力操作の結果としてのみ「アナウンス」されます。したがって、リスト構造を捨てて、各出力操作に対して単に「そして次に行う」ことも考えます。

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

悪くない!

では、これらすべてがモナドとどう関係しているのでしょうか?

実際には、単純なコンストラクタを使用してすべてのプログラムを定義する必要はありません。このような基本的なコンストラクターはいくつか必要ですが、より高レベルなものについては、いくつかの素晴らしい高レベルのシグネチャを持つ関数を記述したいと思います。これらのほとんどは非常によく似ていることがわかります。ある意味のあるタイプの値を受け入れ、結果としてIOアクションを生成します。

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

ここには明らかにパターンがあります。次のように記述した方がいいでしょう。

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

これで見慣れたものになり始めましたが、内部では薄く偽装された単純な関数しか扱っていないため、危険です。各「値アクション」には、含まれている関数の結果として生じるアクション(実際には他のアクション)を実際に渡す責任がありますプログラム全体の制御フローは、途中で1つの不適切な動作によって簡単に中断されます)。その要件を明示的にする方がよいでしょう。まあ、それらはモナドの法則であることがわかりますが、標準のバインド/結合演算子なしで実際にそれらを公式化できるかどうかはわかりません。

とにかく、適切なモナドインスタンスを持つIOの定式化に到達しました。

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

明らかに、これはIOの効率的な実装ではありませんが、原則として使用可能です。


@jdlugosz: IO3 a ≡ Cont IO2 a。しかし、私はそのコメントを、継続モナドをすでに知っている人たちへのうなずきとして、より正確に初心者に優しいという評判がないので、それを意味しました。
左回り、2015年

4

モナドは、繰り返し発生する問題のクラスを解決するための便利なフレームワークにすぎません。まず、モナドはファンクタである必要があります(つまり、要素(またはそのタイプ)を調べずにマッピングをサポートする必要があります)。また、バインディング(またはチェーン)操作と、要素タイプからモナド値を作成する方法(return)も必要です。最後に、bindそしてreturnまた、モナドの法則と呼ばれる2つの方程式(左と右のアイデンティティを)、満たさなければなりません。(あるいは、モナドをflattening operationバインディングの代わりにを持つように定義することもできます。)

リストモナドは、一般的に非決定論に対処するために使用されます。バインド操作は、リストの1つの要素(直感的にはすべての要素をParallel Worldsで)を選択し、プログラマーがそれらを使用していくつかの計算を実行できるようにします。 )。Haskellのモナディックフレームワークで順列関数を定義する方法を次に示します。

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

次に、replセッションの例を示します

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

リストモナドは決して計算に悪影響を与えるものではないことに注意してください。モナドである数学的構造(つまり、上記のインターフェースと法則に準拠)は副作用を意味しませんが、副作用現象はモナドフレームワークにうまく適合します。


3

モナドは基本的に、関数をチェーンで構成するのに役立ちます。限目。

現在、それらの作成方法は既存のモナドによって異なり、その結果、異なる動作になります(たとえば、状態モナドで可変状態をシミュレートするため)。

モナドに関する混乱は、非常に一般的、つまり関数を作成するメカニズムであり、多くのものに使用できるため、モナドは「関数の構成」についてのみであるのに、モナドは状態やIOなどについてであると人々に信じ込ませます。 」

さて、モナドの興味深い点の1つは、コンポジションの結果は常に「M a」タイプ、つまり「M」でタグ付けされたエンベロープ内の値であることです。この機能は、たとえば、純粋なコードと純粋でないコードを明確に区別して実装するのに本当に便利です。すべての純粋でないアクションを「IO a」型の関数として宣言し、IOモナドを定義するときに、「 「IO a」内の「a」値。結果は、純粋なままでいる間はそのような値を取得する方法がないため(使用する関数は「IO」モナド内にある必要があるため)、純粋な関数はなく、同時に「IO a」から値を取得できません。そのような値)。(注:まあ、完璧なものは何もないため、「unsafePerformIO:IO a-> a」を使用して「IO拘束ジャケット」を壊すことができます


2

型コンストラクター、その型ファミリーの値を返す関数がある場合は、モナドが必要です。最終的には、これらの種類の機能組み合わせる必要があります。これらは、その理由に答えるための 3つの重要な要素です。

詳しく説明します。あなたは持っているIntStringRealし、タイプの機能Int -> StringString -> Realおよびオンそう。これらの関数は簡単に組み合わせることができ、末尾はInt -> Realです。人生は素晴らしい。

その後、ある日、新しいタイプのファミリーを作成する必要があります。値を返さない(Maybe)、エラーを返す(Either)、複数の結果(List)などの可能性を考慮する必要があるためです。

これMaybeは型コンストラクタです。のような型を取り、Int新しい型を返しますMaybe Int。最初に覚えておかなければならないのは、型コンストラクターもモナドもありません。

もちろん、あなたはあなたのタイプのコンストラクタを使用したい、あなたのコード内で、すぐに次のような機能で終わるInt -> Maybe StringString -> Maybe Float。これで、関数を簡単に組み合わせることができなくなりました。人生はもう良くありません。

そして、ここでモナドが助けに来ます。それらを使用すると、そのような機能を再び組み合わせることができます。あなただけの構成を変更する必要があります。> ==


2
これはタイプファミリーとは関係ありません。あなたは実際に何を話しているのですか?
dfeuer 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.