ダン・ピポニ

抂芁この蚘事では、モナドが必芁になる可胜性のある゜リュヌションのタスクの䟋を瀺したす。



参加する代わりに翻蚳開始



倚くの「モナドの玹介」では、モナドは説明が難しいものずしお提瀺されたす。 これは実際には耇雑なものではないこずを瀺したいず思いたす。 実際、関数型プログラミングのさたざたな問題に盎面するず、倚くの堎合モナドの䟋であるさたざたな解決策に断固ずしお出くわしたす。 そしお、ただ孊んでいないなら、それらを発明するこずを孊ぶこずを願っおいたす。 今埌は、これらの゜リュヌションは本質的に同じ゜リュヌションであるず蚀えたす。 読んだ埌、あなたはおそらくあなたが自分が発明したものずしお芋るものすべおを認識するので、おそらくモナドの他の䜜品をよりよく理解するでしょう。



モナドが解決しようずしおいる倚くの問題は、「副䜜甚」の問題に関連しおいたす。 そしお、それらから始めたす。 私はモナドが副䜜甚を扱う胜力よりもはるかに倚くのこずを可胜にしおいるこずに泚意しおください、特にコンテナオブゞェクトの倚くはモナドずしお衚珟できたす。



副䜜甚玔粋な機胜のデバッグ





C ++などの呜什型プログラミング蚀語では、関数は数孊的な意味で関数ずは異なる動䜜をしたす。 たずえば、C ++関数が1぀の匕数、浮動小数点数を受け取り、浮動小数点数を返すずしたす。

倖芋䞊は、RからRぞの数孊関数のように芋えたすが、C ++関数は、匕数に䟝存する単なる数倀を返す以䞊のこずができたす。 グロヌバルメモリから倀を読み曞きしたり、画面に出力を曞き蟌んだり、ナヌザヌから入力を受け取ったりできたす。 玔粋な関数型蚀語では、関数は匕数を介しお枡されたもののみを読み取るこずができ、倖郚の䞖界に圱響を䞎える方法は1぀しかありたせん-発行される倀です。



したがっお、玔粋なプログラミング蚀語での次の問題を考えおみたしょう。関数fずgがR-> Rずしお機胜し、これらの関数を倉曎しおデバッグ情報も出力するようにしたす。 Haskellでは、fずgは次のタむプを持぀こずができたす



f,g :: Float -> Float







副䜜甚を蚱容するために、タむプfずgをどのように倉曎できたすか 実際、遞択の䜙地はありたせん。 fずgで文字列ず浮動小数点数を䜜成する堎合、唯䞀可胜な方法は、浮動小数点数ずずもに文字列を返すこずです。 蚀い換えれば、関数f 'ずg'は型を持たなければなりたせん



f',g' :: Float -> (Float,String)







これを図に描くこずができたす。

     x
     |
   + --- +
   |  f '|
   + --- +
    |  \ 
    |  |
   fx "fが呌び出されたした。"




それらを「デバッグ関数」ず呌びたす。



このような2぀の関数の構成をデバッグするずしたす。 初期関数fずgの単玔な構成をfの圢匏で蚘述できたす。 g 。 ただし、デバッグ機胜の堎合、これを盎接行うこずはできたせん。 f 'ずg'によっお返される行を、共通のデバッグ行 g 'から1぀、およびf'から1぀埌の行に結合する必芁があるためです。 ただし、 g 'の戻り倀の型がf'の匕数の型ず䞀臎しないため、 f 'ずg'を盎接結合するこずはできたせん。 ゜リュヌションコヌドは次のように蚘述できたす。



let (y,s) = g' x

(z,t) = f' y in (z,s++t)









これを図で瀺したす



      x
      |
    + --- +
    |  g '|
    + --- +
     |  \   
   + --- + |  「gが呌ばれた。」
   |  f '|  |
   + --- + |
    |  \ | |
    |  \ | |
    |  + ---- +
    |  |  ++ |
    |  + ---- +
    |  |
 fgx「gが呌び出されたした。fが呌び出されたした。」




毎回、この方法で2぀の関数の構成を蚘述するこずは難しく、コヌドのどこにでもこのようなバむンディングを蚘述したい堎合、それは難しいタスクになりたす。 必芁なのは、このバむンディングを実行するために高階関数を定矩するこずです。 難点は、出力g 'を入力f'に䟛絊できず、 f 'を「改善」する必芁があるこずです。 これを行うには、次のタむプの関数を導入し、 bindず呌ぶ必芁がありたす



bind f' :: (Float,String) -> (Float,String)







そのように動䜜したす



bind :: (Float -> (Float,String)) -> ((Float,String) -> (Float,String))







bindは2぀のタスクを実行する必芁がありたす。1目的の郚分g 'xに f'を適甚し、2 g 'によっお返される文字列ずf'によっお返される文字列を結合する必芁がありたす。



挔習1



バむンド関数を䜜成したす。



解決策



bind f' (gx,gs) = let (fx,fs) = f' gx in (fx,gs++fs)







これで、デバッグ関数f 'ずg'の構成を蚘述できたす。 それらの組成をf '°g'ずしお瀺したす。 gの出力が「 fの入力ず互換性がない」ずいう事実にもかかわらず、これらの操䜜を組み合わせる簡単な方法がありたす。 そしお、これは次の質問に぀ながりたす「単䞀の」機胜の存圚。 通垞、1恒等倉換には次のプロパティf×1 = fおよび1×f = fがありたす。 unit°f = f°unit = fのようなデバッグ関数ナニットを呌び出したしょうを芋぀けるこずができたす。 明らかに、空のデバッグ情報を衚瀺する関数を䜜成できたす。そうでない堎合は、同䞀の倉換ずしお機胜したす。



挔習2



unitを入力したす。



解決策

unit x = (x,"")







ナニット関数を䜿甚するず、関数をデバッグスペヌスに「持ち䞊げる」こずができたす。 実際には



lift fx = (fx,"")







たたは簡単にリフトf =単䜍。 f 「raised」関数は元の関数ず同じように機胜し、論理的には副䜜甚ずしお空の文字列を衚瀺したす。



挔習3



リフトf°リフトg =リフトf。gであるこずを瀺す



芁玄するず、 バむンド関数ずナニット関数を䜿甚するず、デバッグ関数を盎接䜜成し、通垞の関数ずデバッグを自然な方法で組み合わせるこずができたす。



信じられないかもしれたせんが、緎習を終えお最初のモナドを特定したした。 今、おそらくどの構造がモナドであるか、たたは他のモナドがどのように芋えるかはあたり明確ではありたせん。 しかし、ここでモナドを定矩する代わりに、他のより簡単な挔習を䜿甚しお、その名前に倀する䞀般的な構造を自分で理解できるように他のモナドをもたらしたす。 たた、この問題に遭遇したほずんどの人は、 バむンド機胜を掚枬しおデバッグ機胜を組み合わせるこずができるず確信しおいたす。 たた、モナドであるこずに気付いおいなくおも、すでにモナドを思い付くこずができるず確信しおいたす。



コンテナ倚倀関数





それぞれ実数の平方根ず立方根を蚈算する関数sqrtずcbrtを考えおください。 これらの関数は、FloatからFloatに䜜甚したす sqrtは負の匕数に察しおも䟋倖をスロヌする必芁がありたす。



次に、これらの関数の耇雑なバヌゞョンを䜜成したす。 れロを陀くすべおの耇玠数に぀いお、2぀の平方根が必芁です。 同様に、れロ以倖のすべおの耇玠数には3぀の立方根がありたす。 したがっお、関数sqrt 'およびcbrt'を定矩しお、数倀のリストを返したす。



sqrt',cbrt' :: Complex Float -> [Complex Float]







それらを倚倀関数ず呌びたす。



実数の6番目の根を芋぀けたいずしたす。この堎合、平方根ず立方根を単玔に組み合わせるこずができたす。 ぀たり、6番目のルヌトをx = sqrtcbrt xずしお定矩できたす。



sqrt 'およびcbrt'を䜿甚しお、耇玠数の6乗の6぀の根をすべお返す関数を考えたす。 これらの関数の和集合だけを曞き留めるこずはできたせん。 最初に数倀の3乗根を蚈算し、次に取埗した倀の平方根を蚈算し、すべおの結果を1぀の長いリストに結合する必芁がありたす。 これを行うには、定矩を䜿甚しお関数を䜜成する必芁がありたす bindを呌び出したしょう



bind :: (Complex Double -> [Complex Double]) -> ([Complex Double] -> [Complex Double])







完党なプロセスを説明する図を次に瀺したす。 cbrtを 1回曞きたいが、結果の倀にsqrtを適甚したい。

         64
         |
      + ------ +
      + sqrt '+
      + ------ +
    +8 / \ -8
  + ------ + + ------ +
  |  cbrt '|  |  cbrt '|
  + ------ + + ------ +
   |  |  |  |  |  |
   2。  。  -2。  。




挔習4



バむンド実装を䜜成したす。



解決策



bind fx = concat (map fx)







倚倀関数のナニットを䜜成するにはどうすればよいですか ナニットは1぀の匕数を返すため、関数の耇数倀バヌゞョンでは、1぀の芁玠で構成されるリストを返す必芁がありたす。 ナニット関数を呌び出したす。



タスク5



レコヌド単䜍。



解決策



unit x = [x]







たた、 f°g = bind fを定矩したす。 gおよびリフトlift f = unit。 f Raisingは期埅したこずを行いたす-通垞の関数を明瀺的に倚倀関数に倉換したす。



挔習6



f°unit = unit°f = fおよびlift f°lift g = liftfgであるこずを瀺す



繰り返しになりたすが、䞊蚘の問題により、容赊なくバむンド機胜に至りたした。



挔習を完了した埌、2番目のモナドを䜜成したした。 おそらく、あなたはすでに䞀般的な開発パタヌンを理解しおいたす。 これら2぀の異なる倖芳の問題が共通の蚭蚈に぀ながるこずは奇劙です。



より耇雑な副䜜甚の䟋乱数





Haskell乱数は次のずおりです



 ランダム:: StdGen→a、StdGen 




これは、乱数を生成するために粒床が必芁であり、生成埌に曎新する必芁があるずいう考え方です。 非玔粋な蚀語では、カヌネルをグロヌバル倉数にするこずができ、ナヌザヌはそれを盎接操䜜する必芁がありたせん。 ただし、玔粋な蚀語では、グレむンを明瀺的に送受信する必芁がありたすこれは、ランダム性を衚すいわゆる眲名です。 この問題は、ペアを䜿甚しお远加情報を返すずいう点で、䞊蚘のデバッグの堎合ず䌌おいるこずに泚意しおください。 しかし、この問題では、远加情報も送信したす。



したがっお、ランダム関数a- > bである関数は 、関数a→StdGet→b、StdGenずしお曞き換えるこずができたす。ここで、 StdGenはシヌドです。



ここで、2぀のランダム関数fずgの構成を䜜成する方法を理解する必芁がありたす。 fの「実際の」戻り倀は、最初の匕数gに枡す必芁がありたす。 䞀方、ペアの2番目の芁玠によっおfによっお返されるグレむンもgに転送する必芁がありたす。 このようにしお、バむンド関数のタむプを蚘述できたす。



  bind ::a→StdGen→b、StdGen→StdGen→a、StdGen→StdGen→b、StdGen 




挔習7



バむンドを実装する



解決策



  bind fx seed = letx '、seed'= x seed in fx 'seed' 




次に、ナニットを䜜成する必芁がありたす。 圌女はタむプでなければなりたせん



 ナニット:: a→StdGen→a、StdGen 




穀物は倉曎しないでください。



挔習8



ナニットを実装したす。



解決策



 単䜍xg =x、g 




たたは単に



 単䜍=、 




たた、操䜜f°g =バむンドfgおよびリフトlift f =ナニットを定矩できたす。 f。 Raisingは想定されおいるこずを行いたす-通垞の関数をランダム関数ランダム化に倉換し、カヌネルを倉曎したせん。



挔習9



f°unit = unit°f = fおよびlift f°lift g = liftfgであるこずを瀺す



モナド





それでは、最初に戻っお党䜓的な構造に泚目しおください。



定矩する



タむプDebuggable a =a、String
タむプMultivalued a = [a]
タむプRandomized a = StdGen->a、StdGen




倉数mを䜿甚しお、Debuggable、Multivalued、たたはRandomizedを実装したす。 いずれの堎合も、同じ問題に盎面しおいたす。 -> mb関数がありたすが、この関数をタむプaではなくオブゞェクトタむプmaに適甚できる必芁がありたす。 これを行うには、タむプa-> mb->ma-> mbのバむンディング関数 bindず呌ばれるを定矩し、恒等倉換ナニット:: a-> maを導入したす。 さらに、芁件を満たす必芁がありたす f°unit = unit°f = fおよびlift f°lift g = liftfg 、ここで ° およびliftはunitおよびbindの芳点から定矩されたす。



今、私はモナドずは䜕かを蚀うこずができたす。 オブゞェクトのトリプルm、ナニット、バむンドはモナドですが、モナドであるためには、䞊蚘で蚌明した法則を満たさなければなりたせん。 そしお、3぀のケヌスすべおが共通の構造によっお結合されおいるこずに気付いおいなくおも、3぀のケヌスのそれぞれでバむンド関数を発明できるず思いたす。



それで、私はこれをHaskellモナドの定矩に関連付けなければなりたせん。 そしお、「 bind 」ずいう単語で最初に反映しお指定したのは、 バむンド関数の定矩です。これは>> =挔算子ずしお蚘述されおいたす 。 次に、結合fxは x >> = fずしお曞き換えられたす。 次に、 ナニットはreturnず呌ばれたす。 そしお第䞉に、関数>> =およびreturnをオヌバヌラむドするには、型クラスを䜿甚する必芁がありたす。 Haskellでは、DebuggableはWriterモナド、MultivaluedはListモナド、RandomizedはStateモナドです。 次のものの定矩を確認する堎合



Control.Monad.Writer Control.Monad.List Control.Monad.State



構文糖に正確に衚瀺されたす。これらは䞊で曞いた定矩です。 それがモナドに出䌚った方法です



モナド構文





私はこれに倚くの時間を費やしたくありたせんそしお、このセクションはスキップできたす。 より良い玹介がたくさんありたす。



バむンド関数がどのように関数をバむンドする䟿利な方法を提䟛し、非垞にいコヌドを曞くこずからあなたを救うこずができるかを芋おきたした。 しかし、Haskellはさらに進んでおり、 バむンド関数を盎接䜿甚する必芁さえありたせん。Haskellに自動的に挿入するように「尋ねる」こずができたす。



公匏の型クラスを䜿甚しお、元のデバッグの䟋に戻りたしょう。 ペアa、sを䜿甚した堎合、 Writer Char型のWriter a、sを䜿甚したす。 このペアを取埗するには、 runWriter関数を䜿甚したす。 1を足し、2を掛け、7を匕いお、各ステップで行うこずを曞き留めたいずしたす。 曞き蟌める合蚈



 return 7 >> =\ x-> Writerx + 1、 "inc。"
     >> =\ x-> Writer2 * x、 "double。"
     >> =\ x->ラむタヌx-1、 "dec。"




この結果にrunWriter関数を適甚するず、15、“ inc.double.dec。”が埗られたす。 しかし、コヌドはただきれいではありたせん。 それを改善するには、do構文を䜿甚したす。 アむデアは



 x <-yを行う
    より倚くのコヌド




コンパむラによっお自動的に曞き換えられたす



 y >> =\ x-> do
                より倚くのコヌド。




たた



する
     x = yずする
    より倚くのコヌド




に曞き換えられたす



 \ x-> do
        より倚くのコヌドy




そしお



する
    衚珟




ただ匏を残したす。



次のようにコヌドを曞き換えるこずができたす。

する
     x = 7ずする
     y <-ラむタヌx + 1、 "inc \ n"
     z <-ラむタヌ2 * y、 "double \ n"
    ラむタヌz-1、「dec \ n」




この゚ントリは非垞に明らかです。 y <-...ず曞くず、右蟺の匏はx + 1であるず仮定できたすが、操䜜によっお副䜜甚が発生したす。



別の䟋。 6次の根を芋぀ける関数を扱いにくい圢で蚘述したす



  return 64 >> =\ x-> sqrt 'x>> =\ y-> cbrt' y 




より読みやすいようにコヌドを曞き盎すこずができたす

する
     let x = 64
     y <-sqrt 'x
     z <-cbrt 'y
     zを返す




通垞のあいたいでないコヌドのように芋えるコヌドを蚘述し、Haskellが自動的に挿入するバむンドバむンディングを暗黙的に呌び出しお、あいたいにするこずができたす。



このような構文の䜜成は倩才の仕事です。 たぶん、あなたはそれを発明するこずができたしたが、私はできなかったず確信しおいたす。 しかし、これらの远加は、実際にはモナドの䞊にある単なる構文䞊の砂糖です。 基瀎ずなるモナドデバむスを思い付くこずができるず確信しおいたす。



入力/出力





モナドを完党に理解する前に泚意しなければならないこずがもう1぀ありたす。 倖の䞖界ずのコミュニケヌション。 䞊蚘で曞かれたものはすべお、玔粋な関数型蚀語に適甚できたす。 しかし、今床は怠pureで玔粋な関数型蚀語に泚目したしょう。 そのような蚀語では、物事がどのような順序で蚈算されるかわかりたせん。 したがっお、「数字を入力しおください」ずいうメッセヌゞを衚瀺する関数ず、数字を芁求する別の関数がある堎合、数字が芁求される前にメッセヌゞが曞き蟌たれるこずを保蚌できたせん。 ランダム関数の䟋を芋おください。ランダムカヌネルがすべおの関数をどのように通過しお、 randomを呌び出すたびに䜿甚できるようになっおいるこずに泚目しおください。 䞀定の順序がありたす。 x >> = f >> = gがあるずしたす gはfによっお返されるグレむンを䜿甚したす。fが gの前に番号を生成するこずを確認できたす。 そしおこれは、原則ずしお、モナドを䜿甚しお蚈算を合理化できるこずを瀺しおいたす。



次に、コンパむラでの乱数の実装を提案したす。 これは、Haskell実行可胜ファむルに挿入されたCたたはアセンブラヌコヌドです。 このコヌドはI / Oを実行するように倉曎されおいたす。gの前にfが実行されるこずを保蚌できたす。 これは、I / OがHaskellで動䜜するためです。モナドですべおのI / Oを実行したす。 この堎合、タむプa-> bの関数ず倖界の副䜜甚は、タむプa-> IO bになりたす。 タむプIOはブラックボックスです。その䞭に䜕があるかを知る必芁はありたせん。 たぶんそれはランダムな䟋ずしお機胜するかもしれたせんが、そうではないかもしれたせんx >> = f >> = gfはgの前に実行されるこずを知る必芁がありたす。



カテゎリヌ理論





そしお最埌に。 モナドはカテゎリヌ理論の文脈で発明されたした。 そしお、私は次の日にこの接続を離れたす。



補遺乱数を䜿甚した完党なサンプルコヌド





むンポヌトランダム

 bind ::a-> StdGen->b、StdGen->StdGen->a、StdGen->StdGen->b、StdGen
 bind fx seed = letx '、seed'= x seed in fx 'seed'
単䜍xg =x、g
リフトf =単䜍。  f




そしお、私たちがやろうずしおいるこずこれらの手順に埓うこずで210進数の数字を構築したい



1[0.9]の範囲の乱数を䜜成したす

210を掛けたす

3範囲[0.9]から別の乱数を远加したす



䞀般に、これはaddDigitのようなものの合成です。 * 10。 addDigit 。 しかし、すべおのコヌドにランダムな粒床を枡す必芁があるこずを知っおいたす。 たず、 addDigitの定矩を怜蚎したす。



  addDigit ng = leta、g '=ランダムg inn + a `mod` 10、g' 




n +乱数ず新しいシヌドで構成されるペアを返したす。 圌は远加の匕数ずしお穀物を䜿甚しおいるこずに泚意しおください。 関数はaddDigit n = let a = random in n + a `mod` 10であり、非玔粋な蚀語で移怍可胜であるず考えるこずができたす。



次に、10を掛ける操䜜を玹介したす。これは通垞の操䜜で、リフトを䜿甚しお「改善」できたす。 取埗したす



 シフト=リフト* 10 




そしお、これが決定的な瞬間です。 合成するこずはできたせんが、代わりにバむンディングを䜿甚しお、関数を組み合わせ可胜な圢匏に倉換できたす。 合蚈addDigit。 * 10.addDigitは次のようになりたす



テスト::æ•Žæ•°-> StdGen->敎数、StdGen
 test = addDigitをバむンドしたす。 バむンドシフト。  addDigit




次に、お気に入りの番号を䜿甚しお穀物を䜜成し、コヌドを実行したす



 g = mkStdGen 666
 main = print $ test 0 g




サンプルを曞くずき、私は特にMonad型クラスを䜿甚しなかったこずに泚意しおください。 したがっお、䜕もあなたから隠されず、すべおが明瀺的に行われたした。



All Articles