別のモナドガむドパヌト3モナドの法則

マむク・ノァニ゚

前の蚘事で、 モナド型クラスの2぀の基本的な単項挔算に぀いお説明したした。バむンド挔算子バむンド、 >> = ず戻り関数です。 この蚘事では、 モナド型クラスの定矩に至り 、モナドの法則に぀いお説明したす。

完党なモナド型クラス


Monad型クラスの定矩党䜓を芋おみたしょう。

クラス Monad m ここで
 >> =  :: m a ->  a- > m b  -> m b
return :: a- > m a
 >>  :: m a- > m b- > m b
fail :: 文字列 -> m a


よく知られおいるのは、同じタむプの>> =挔算子ずreturn関数ですが、それらに加えお>>挔算子ずfail関数もありたす。 圌らはどういう意味ですか

倱敗関数は、最初ぱラヌを報告する非垞に原始的な方法です。 >> =挔算子が䞀臎゚ラヌのためにタむプaの倀をタむプa-> mbの関数にバむンドできない堎合に呌び出されたす。 退屈なため、このメカニズムの詳现には觊れたせん。 必芁に応じお、 Haskell Webサむトのドキュメントを参照しおください。 ほずんどの堎合、 倱敗の心配は必芁ありたせん。

>>挔算子はもう少し興味深いものです。 圌はこのタむプを持っおいたす

 >>  :: m a- > m b- > m b


この挔算子は、単項シヌケンス挔算子です。 特に、モナドアプリケヌションの倉圢 >> =たたは「バむンド」で、タむプmbの 「アクション」を実行する前に、タむプaのアンパックされた倀を砎棄したす。 次のように定矩されたす。

mv1 >> mv2 = mv1 >> =  \ _- > mv2 


ここで、モナド倀mv1からアンパックされた倀はすべお拒吊され、最終的なモナド倀mv2が返されるこずがわかりたす。 挔算子は、展開された倀の型がの堎合、぀たり空の型である堎合に圹立ちたす。 putStrLn関数は良い䟋ず考えるこずができたす。

putStrLn :: 文字列 -> IO  


2行を次々に印刷し、各行の埌に改行を入れたいずしたす。 これを行うこずができたす

putStrLn 「これはストリング1です。」 >> putStrLn "これは文字列2です。"


そしお、なぜこれが機胜するのですか タむプを芋おみたしょう。

 putStrLn "これは文字列1です。"  :: IO  
 putStrLn "これは文字列2です。"  :: IO  


぀たり、 >>挔算子は、タむプIOの 2぀のモナド倀を、タむプIOの 1぀の結果のモナド倀に結合したす。 >>挔算子を䜿甚しお、このケヌスに特化しおみたしょう。

 >>  :: m a- > m b- > m b


mがIOで、 aずbがの堎合、次のようになりたす。

 >>  :: IO   -> IO   -> IO  


曞くこずで、おそらく、 >>挔算子が2぀の「アクション」を連続しお実行する、぀たり行を出力するず蚀うこずができたす。

より耇雑な䟋です。 端末からテキスト行を読み取り、2回印刷する必芁がありたす。 次のようにできたす。

readAndPrintLineTwice :: IO  
readAndPrintLineTwice = getLine >> =  \ s ->  putStrLn s >> putStrLn s  


挔算子の優先順䜍のため、レコヌドは角括匧なしで残すこずができたす。

readAndPrintLineTwice :: IO  
readAndPrintLineTwice = getLine >> = \ s- > putStrLn s >> putStrLn s


これはどういう意味ですか getLine-モナド倀「アクション」。端末からテキスト行を読み取りたす。 >> =挔算子は、この文字列をモナド倀から「アンパック」し、名前sに関連付けたす \ s-> putStrLn s >> putStrLn sは、モナド関数、 >> =挔算子の2番目の匕数であるため。 次に、 sずいう行が、モナドの倀putStrLn s >> putStrLn sによっお䜿甚され、連続しお2回出力されたす。

あなたが蚀うこずが神秘的に思えるなら、それはあなたのせいではありたせん。 ここで奇劙なこずが起こっおいたすが、ステヌトモナドに぀いお話をするたでは説明できたせん。 しかし、今では 、ただどのように起こっおいるのかを完党に理解しおいない堎合でも、 䜕が起こっおいるのかを远跡できなければなりたせん。

今、少し戻っおモナドの法則を芋おいきたす。 これらは、特定のモナドごずに>> =挔算子ず戻り関数を䜿甚する際に倧きな圹割を果たしたす。 その埌、より実甚的な資料に移りたす。

モナディズムの䞉法則


倚くの重芁な法則は3぀のグルヌプに分類されたす。ニュヌトンの3぀の機械法則、熱力孊の3぀の法則、Azimov Roboticsの3぀の法則、惑星運動の3぀のケプラヌ法則などです。 もちろん、「3぀のモナドの法則」が䜕よりも重芁であるこずを陀いお、このモナドは違いたせん。 ;-)

>> =挔算子ず戻り関数が特定のモナドに察しお有効であるためには、それらはそのモナドに察しお正しい型を持たなければなりたせん。 したがっお、たずえば、定矩>> =およびMaybeモナドの戻り倀には、そのタむプが含たれたす。

 >> =  :: たぶん a ->  a- > たぶん b  -> たぶん b
return :: a- > たぶん a


そしお、モナドの堎合、 IOにはIOタむプが含たれたす

 >> =  :: IO a ->  a- > IO b  -> IO b
return :: a- > IO b


ただし、これでは十分ではありたせん。 これらの関数/挔算子は、3぀の「モナドの法則」を満たすためにも必芁です。 モナドの法則は実際には非垞に単玔です。 これらは、モナド構成が予枬可胜な方法で機胜するように蚭蚈されおいたす。 最初にモナドの法則の「玠敵な」バヌゞョンを提䟛し、次に通垞説明されおいるようにugい方法を瀺したす。 ありがずうございたす。「玠敵な」オプションの方がずっず理解しやすいです。

玠敵なバヌゞョン

以䞋は、モナド合成の芳点から衚珟された3぀のモナド則の玠晎らしい定矩です挔算子> =>は関数合成のモナド挔算子であるこずを思い出しおください

1 return > => f == f
2 。 f > => return == f
 。  f > => g  > => h == f > =>  g > => h 


これらの法埋は私たちに䜕を䌝えおいたすか

芏則1ず2は、 戻り倀を指定したす。これは、関数の単項合成の単䜍䞭立芁玠です最初の芏則は、戻り倀が巊偎の単䜍で、2番目の芏則が右偎の単䜍であるこずを瀺したす。 ぀たり、単項関数fを䜜成しお 任意の順序で返すず、単に関数fが返されたす。 アナログは、0-敎数を加算する機胜の䞭立芁玠、1-敎数乗算機胜の䞭立芁玠ず芋なすこずができたす。 いずれの堎合も、察応する関数を䜿甚しお通垞の倀に接続されたニュヌトラル芁玠は、この倀を単玔に返したす。

法則3は、合成の単項関数は結合的であるず述べおいたす。3぀の単項関数 f 、 g 、 h を組み合わせたい堎合、どちらを最初に接続しおもかたいたせん。 これは、敎数に適甚したずきに加算ず乗算も結合するずいう事実に類䌌しおいたす。

これらの法埋はあいたいになじみがないように思われたせんか 察応する「法則」を芋おみたしょう。これは通垞の合成機胜によっお満たされたす。

1 id f == f
2 。 f id == f
 。  f。g  。 h == f 。  g。h 


idは䞭立芁玠、ナニットです。 類䌌点を芋぀けたしたか ナニットを巊たたは右に配眮した関数を合成するず、同じ関数が再び䞎えられ、合成関数は結合性になりたす。 コンポゞションのモナド関数は連想的でなければならず、 戻り倀は単䞀の関数のモナド同等物でなければならないので、モナド合成の動䜜は通垞のコンポゞションの動䜜ず同様に予枬可胜です。

プログラマヌの芳点からこれらの法埋の意味は䜕ですか モナドがむンテリゞェントに動䜜するようにしたいので、 returnず>> =の定矩はこれらの法則を満たさなければなりたせん。 >> =およびreturnの定矩を確認する方法をすぐに孊習したす。 [モナドの法則は>> =挔算子ではなく>>>挔算子で衚されたすが、 >> =を䜿甚しおバヌゞョンが衚瀺されるこずに泚意しおください。これは同等です。]

ただし 、キャッチがありたすHaskell はモナドの法則をチェックしたせん  チェックされるのは、 戻り倀ず>> =定矩タむプが正しいこずだけです。 法埋が実斜されおいるかどうかにかかわらず、プログラマは確認する必芁がありたす。

倚くの人が「なぜHaskellがモナドの法則をチェックできないのか」ず尋ねたす。答えは簡単ですHaskellはただ匷力ではありたせん モナドの法則の正しさを蚌明する十分に匷力な蚀語を取埗するには、定理蚌明者定理蚌明者のようなものが必芁です。 定理の蚌明は息をのむようなものであり、プログラミングの未来かもしれたせんが、埓来のプログラミング蚀語よりもはるかに耇雑です。 興味があれば、Coq定理の尊敬される蚌拠がありたす 。それはここから入手できたす 。 しかし、Haskellでは、プログラマヌは、自分が曞いたモナドがモナドの法則に違反しないように泚意する必芁がありたす。

glyいバヌゞョン

niceバヌゞョンの問題は、 > =>挔算子がMonad型クラスで盎接定矩されおいないこずです。 代わりに、䞊で瀺したように、 >> =挔算子が定矩され、 > =>挔算子が掟生したす。 したがっお、定矩を>> >>およびreturn挔算子に制限する堎合、 returnおよび>> =のみを含むモナド則が必芁です。 たた、この圢匏では、前のセクションで瀺したものよりも盎感的ではありたせんが、Haskellのモナドに関するほずんどの曞籍やドキュメントで提䟛されおいたす。

>> =挔算子ず戻り関数に関しお、モナドの法則は次のようになりたす。

1 return x >> = f == f x
2 。 mv >> = return == mv
 。  mv >> = f  >> = g == mv >> =  \ x ->  f x >> = g  


ここで、異なる倀のタむプは次のずおりです。

mv :: m a
f :: a- > m b
g :: b- > m c


いく぀かのタむプa 、 b 、 cおよびある皮のモナドmに察しお 。

玠敵なバヌゞョンからモナド則のいバヌゞョンを掟生させる

楜しんで、玠敵なバヌゞョンからモナド則のいバヌゞョンを掚枬しおみたしょう。 蚈算では、䞊蚘で怜蚎したモナド合成の定矩が必芁です。

f > => g = \ x ->  f x >> = g 


法埋1

return > => f == f
\ x ->  return x >> = f  == \ x- > f x
return x >> = f == f x -QED「蚌明するために必芁」


\ x-> fxは fず同じであるこずに泚意しおください。

法埋2

f> => return == f
\ x->fx >> = return== \ x-> fx
fx >> = return == fx
let mv == fx
mv >> = return == mv-QED

法埋3

 f > => g  > => h == f > =>  g > => h 
\ x ->   f > => g  x >> = h  == \ x ->  f x >> =  g > => h  
 f > => g  x >> = h == f x >> =  g > => h 
 \ y ->  f y >> = g   x >> = h == f x >> =  \ y ->  g y >> = h  
-蚈算\ y->f y >> = gx取埗f x >> = g
 f x >> = g  >> = h == f x >> =  \ y ->  g y >> = h  
-mv = f xずし、次に
 mv >> = g  >> = h == mv >> =  \ y ->  g y >> = h  
-gをfに、hをgに眮き換えたす。
 mv >> = f  >> = g == mv >> =  \ y ->  f y >> = g  
-正しい匏でyをxに眮き換えお、以䞋を取埗したす。
 mv >> = f  >> = g == mv >> =  \ x ->  f x >> = g   -QED


蚈算ステップ\ y->fy >> = gxでは、単に関数党䜓 \ y-> ... を匕数xに適甚したす。 この堎合、 yは関数本䜓の倉数xに眮き換えられ省略蚘号...で瀺されたす、関数本䜓が結果ずしお返されたす。 Lingo関数型プログラミング蚀語では、この操䜜はベヌタ削枛ず呌ばれたす。 {1この堎合、我々はラムダ蚈算ず呌ばれる数孊のセクションに぀いお話しおいる。これは、ずりわけベヌタ削枛を蚘述しおいる。}ベヌタ削枛は、関数を蚈算する䞻な方法である。 次の2぀の関数が正しいため、 yがxで眮き換えられる最埌のステップは正しいです。

\ x- > f x
\ y- > f y


-これはたったく同じです正匏な匕数の名前は重芁ではありたせん。 Lingo関数型蚀語では、2぀の関数はアルファ版ず同等です。 他の手順を理解しおいる必芁がありたす。

アむデアは䜕ですか

モナドの法則は、コヌドで時々䜿甚され、長い匏を短い匏に眮き換えたすたずえば、 return x >> = fの代わりに、単にfxを曞くこずができたす。 ただし、次の蚘事では、モナドの法則の䞻な利点は、特定のモナドのリタヌンず>> =の定矩を導出できるこずです。

この蚘事の最埌で、きちんずした構文圢匏の蚘述を瀺したいず思いたす。その助けを借りお、モナドのコヌドがより良く芋えるようになりたす。

抜象的



䞊蚘で定矩したreadAndPrintLineTwice関数を思い出しおください。

readAndPrintLineTwice :: IO  
readAndPrintLineTwice = getLine >> = \ s- > putStrLn s >> putStrLn s


圌女には1぀の利点がありたすそれは1行で曞かれおいたす。 欠点は、䞖界で最も読みやすい行ではありたせん。 Haskellの蚭蚈者は、モナド定矩を読むのが難しいこずが倚いこずに気付き、定矩を読みやすくする本圓に玠晎らしい構文糖を芋぀けたした。

この構文糖の基瀎は、モナドコヌドの膚倧な数の操䜜が2぀の圢匏で蚘述されおいるずいう芳察です。

-フォヌム1。
-mv :: m a
-f :: a-> m b

mv >> = \ x- > mf x

-フォヌム2。
-mv :: m a
-mv2 :: m b

mv >> mv2


衚蚘法は、これらの2぀の圢匏を読みやすくするこずを目的ずしお蚭蚈されたした。 doキヌワヌドで始たり、その埌にいく぀かの単項挔算が続きたす。 したがっお、これらの2぀の䟋はdo衚蚘で蚘述されたす 。

-フォヌム1、衚蚘法。
do v < -mv
f v

-フォヌム2、衚蚘法。
MVを行う
mv2


フォヌム1の最初の行は、モナド倀mvを取埗し、それをvず呌ばれる通垞の倀に「アンパック」するこずを意味したす。 2行目はvからfを蚈算しおいるだけです。 文字列fvの結果は、匏党䜓の結果です。

フォヌム2では、最初の行でモナド倀「アクション」 mvが 「実行」されたす。 2行目は、別のモナド倀「アクション」 mv2を 「実行」したす。 したがっお、 >>挔算子のように、 mvずmv2をシヌケンスに連結する衚蚘法がありたす。

Haskellコンパむラは、䟿利なdo衚蚘をForm 1およびForm 2の非do゚ントリに倉換したす。これは単なる構文倉換であり、䞡方の゚ントリの意味は同じです。 さらに、䞡方の圢匏を各衚蚘法の1぀の衚珟に混圚させるこずができたす。 䟋

-mv :: m a
-v1 :: a
-f :: a-> m b
-v2 :: b
-mv3 :: m c

する
v1 < -mv
v2 < -f v1
mv3
v2を返す


これはたったく同じです

mv >> =  \ v1- >
 f v1 >> =  \ v2- >
 mv3 >>
 v2を返す     


たたはskokbokなし

mv >> = \ v1- >
f v1 >> = \ v2- >
mv3 >> v2を返す


モナド匏が倧きくなっおも、 do- formは読みやすく、 doなし "Sugared"ずも呌ばれるのフォヌムは読みにくくなるこずがよくありたす。 それが、 do- abstractが存圚する理由です。 do- abstractは、1぀だけでなく、 すべおのモナドで機胜するのは玠晎らしいこずです。

さらに、 do衚蚘ずシュガヌレス衚蚘を1぀の匏に混圚させるこずができたす。 このように

do v1 < -mv
v2 < -f v1
mv3 >> v2を返す


これは䟿利な堎合もありたすが、コヌドの可読性が䜎䞋するこずがよくありたす。

前の䟋がdo蚘法でどのように芋えるか芋おみたしょう。

-行を読んでから印刷したす。
readAndPrintLine :: IO  
readAndPrintLine =
する
行< -getLine
putStrLn行

-2行ず぀印刷したす。
-機胜ではありたせん。
する
putStrLn 「これはストリング1です。」
putStrLn 「これは文字列2です。」

-行を読み、2回印刷したす。
readAndPrintLineTwice :: IO  
readAndPrintLineTwice =
する
行< -getLine
putStrLn行
putStrLn行


ここでは、コヌドはdo -notationのおかげで読みやすくなっおいたす。 興味深いこずに、これには远加の利点たたは、保持するビュヌに応じお䞍利な点がありたす。Haskellのコヌドは必須に芋えたす コヌドを䞊から䞋に読むず、矢印<-を割り圓おる代わりに呜什型蚀語のように芋えたす。 readAndPrintLineは次のように蚘述できるずしたしょう。「 getLineを呌び出しお、 行倉数に入れた行を読み取りたす。 次に、 putStrLnを呌び出しおこの倉数を出力したす。」これは、実際には実際には起こりたせんたずえば、 行は倉数ではありたせんが、そのように読むこずができたす。 倚くの入出力を行う倚くのコヌドでは、 do- abstractは非垞に䟿利な蚘述方法です。

衚蚘には他の有甚なプロパティがありたす。 たずえば、 do衚蚘の本文にlet匏ずcase匏を埋め蟌むこずができたす。これはしばしば䟿利です。 これはルヌチンであるため、詳现には觊れたせん。他のHaskellチュヌトリアルを䜿甚しお、この点を調べおください。

次回

次の蚘事では、 Maybe ゚ラヌが発生する可胜性のある蚈算のモナドから始たり、リストモナド耇数の結果を䌎う蚈算の堎合で終わるモナドに぀いお説明したす。

内容

パヌト1基本
パヌト2関数>> =およびreturn
パヌト3モナドの法則
パヌト4倚分モナドずリストモナド

Source: https://habr.com/ru/post/J128538/


All Articles