ハスケル。 モナド。 モナド変換器。 タイプのゲーム

非常に初心者向けのモナドの別の紹介。



モナドを理解する最良の方法は、モナドを使い始めることです。 モナドの法則、カテゴリー理論でスコアを付け、コードを書き始める必要があります。



Haskellでコードを記述することは、オブジェクトを正しい型に変換する必要があるゲームのようなものです。 したがって、最初にこのゲームのルールを理解する必要があります。 コードを書くときは、特定のコードがどのタイプであるかを明確に理解しなければなりません。



通常の機能では、すべてが明確です。 「a-> b」型の関数があり、「a」型の引数をそれに代入すると、「b」型の結果が得られます。



モナドでは、すべてがそれほど明白ではありません。 ネコの下で、do-constructionの操作方法、型の順次変換方法、およびモナド変換が必要な理由について詳しく説明します。



1.設計する


簡単な例から始めましょう。



main = do putStr "Enter your name\n" name <- getLine putStr $ "Hello " ++ name
      
      





各doコンストラクトはma型で、mはモナドです。 私たちの場合、これはIOモナドです。







do-constructionの各行も「ma」タイプです。 各行の「a」の値は異なる場合があります。







文字 "<-"は、そのままで、タイプ "IO String"をタイプ "String"に変換します。



モナドでこのモナドに関連しない計算を実行する必要がある場合、 return関数を使用できます。



 return :: a -> ma main = do text <- getLine doubleText <- return $ text ++ text putStr doubleText
      
      





return関数は、モナド型 "ma"の型 "a"をラップします。







この例では、 returnを使用して、タイプ「String」の式がタイプ「IO String」に変換され、その後「String」に展開されます。 または、doコンストラクト内でletキーワードを使用できます。



 main = do text <- getLine let doubleText = text ++ text putStr doubleText
      
      





do-construction全体が最後の行のタイプを取ります。







ファイルの内容を読み取りたいとします。 このために、 readFile関数があります。



 readFile :: FilePath -> IO String
      
      





ご覧のとおり、関数は「IO文字列」を返します。 ただし、ファイルの内容は「文字列」として必要です。 つまり、do-construct内で関数を実行する必要があります。



 printFileContent = do fileContent <- readFile "someFile.txt" putStr fileContent
      
      





ここで、 fileContent変数は「String」型であり、通常の文字列と同様に作業できます(たとえば、画面に表示する)。 結果のprintFileContent関数のタイプは「IO()」であることに注意してください



 printFileContent :: IO ()
      
      





2.モナドとモナド変換子


次の簡単な例えをします。 モナドは、特定の空間に固有のアクションを実行できる空間であると想像してください。

たとえば、IOモナドでは、テキストをコンソールに出力できます。





 main = do print "Hello"
      
      





モナドの「状態」には、変更可能な外部状態があります。





 main = do let r = runState (do modify (+1) modify (*2) modify (+3) ) 5 print r -- OUTPUT: -- ((), 15)
      
      





この例では、数値5に1を加算し、結果に2を乗算し、さらに3を加算しました。その結果、数値15が得られました。



runState関数を使用する



 runState :: State sa -> s -> (a, s)
      
      





モナドを「起動」します。







モナドは、内側と外側の2つの側面から見ることができます。 内部から、このモナドに固有のアクションを実行できます。 そして外部-それを「起動」、「印刷」、非モナド型に変換できます。



これにより、上記の例のように、doコンストラクトを別のコンストラクトに埋め込むことができます。 IOモナドは、外部から見ることができない唯一のモナドです。 最終的にはすべてがIOに組み込まれます。 IOモナドは私たちの基盤です。



上記の例には特定の制限があります。 Stateモナド内では、 IOで利用可能なアクションを実行できません。







私たちは「空中に浮遊」し、地面との接触を失いました。



この問題を解決するために、 モナド変換器があります。



 main = do r <- runStateT (do modify (+1) modify (*2) s <- get lift $ print s modify (+3) ) 5 print r -- OUTPUT: -- 12 -- ((), 15)
      
      





このプログラムは前のものと同じことをします。 StateStateTに置き換え、 次の 2行を追加しました。



 s <- get lift $ print s
      
      





これを使用して、中間結果をコンソールに表示します。 I / O操作は、「ネストされた」StateTモナド内で実行されることに注意してください。



ここで、 runStateTStateTモナドを起動し、 リフト機能は、 StateTモナドのIOで利用可能な操作を「レイズ」します。



 runStateT :: StateT sma -> s -> m (a, s) lift :: IO a -> StateT s IO a
      
      





この例で、タイプがどのように順次変換されるかを注意深く調べてください。







操作「print s」のタイプは「IO()」です。 リフトの助けを借りて、「StateT Int IO()」タイプに「上げる」。 内部do-constructionのタイプは「StateT Int IO()」になりました。 それを「実行」し、タイプ「Int-> IO(()、Int)」を取得します。 次に、値「5」を代入して、タイプ「IO(()、Int)」を取得します。

タイプ「IO」を取得したため、外部のdo-constructで使用できます。 矢印「<-」はモナド型を削除し、「(()、Int)」を返します。 結果「(()、15)」がコンソールに出力されます。



StateTは、外部状態を変更し、I / O操作を実行できます。 つまり StateTモナドStateのように「 宙に浮かぶ 」ことはありませんが、外部のモノIOと接続されたままです。







したがって、プログラムには、互いにネストされたモナドの束があります。 これらのモナドの一部は互いにリンクされ、一部はリンクされません。



私の類推が、あなたが新しい観点から物事を見るのを助け、あなたが将来モナドの真のマスターになることができることを願っています。










All Articles