誰かが非常に単純な(数行)モナド変換子の例を挙げてもらえますか?.
たとえば、誰かがIOを実行し、失敗を処理できるモナドをどのように作成しますか?
これを示す最も簡単な例は何ですか?
私はいくつかのモナドトランスフォーマーチュートリアルをざっと見てきましたが、それらはすべてState MonadやParsers、または何か複雑なもの(newbeeの場合)を使用しているようです。それよりももっとシンプルなものが見たいです。 IO +は簡単だと思いますが、自分でそれを行う方法がわかりません。
どうすればIO + Maybeモナドスタックを使用できますか?上に何がありますか?下には何がありますか?どうして?
どのような場合に、IO + MaybeモナドまたはMaybe + IOモナドを使用しますか?それはそのような複合モナドを作成することにはまったく意味がありますか?はいの場合、いつ、なぜですか?
これは here を.lhsファイルとして利用できます。
MaybeT
トランスフォーマーを使用すると、例外をスローするのと同じように、モナド計算から抜け出すことができます。
最初に、いくつかの予備知識について簡単に説明します。 にスキップして、実際の例としてIOにパワーを追加するに進みます。
最初にいくつかのインポート:
_ import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
_
経験則:
モナドスタックではIOは常に一番下にあります。
他のIOのようなモナドも、原則として常に下部に表示されます。状態変換モナドST
。
_
MaybeT m
_は、Maybeモナドのパワーをモナドm
に追加する新しいモナドタイプです。 _MaybeT IO
_。
そのパワーについては後で説明します。とりあえず、_MaybeT IO
_をmaybe + IOモナドスタックと考えることに慣れます。
_
IO Int
_がInt
を返すモナド式であるのと同様に、_MaybeT IO Int
_はInt
を返す_MaybeT IO
_式です。
複合型シグネチャの読み取りに慣れることは、モナド変換子を理解するための戦いの半分です。
do
ブロック内のすべての式は、同じモナドからのものでなければなりません。
つまりこれは、各ステートメントがIOモナドにあるため機能します。
_ greet :: IO () -- type:
greet = do putStr "What is your name? " -- IO ()
n <- getLine -- IO String
putStrLn $ "Hello, " ++ n -- IO ()
_
putStr
が_MaybeT IO
_モナドにないため、これは機能しません。
_mgreet :: MaybeT IO ()
mgreet = do putStr "What is your name? " -- IO monad - need MaybeT IO here
...
_
幸い、これを修正する方法があります。
IO
式を_MaybeT IO
_式に変換するには、liftIO
を使用します。
liftIO
は多態的ですが、この例では次のタイプになります。
_liftIO :: IO a -> MaybeT IO a
mgreet :: MaybeT IO () -- types:
mgreet = do liftIO $ putStr "What is your name? " -- MaybeT IO ()
n <- liftIO getLine -- MaybeT IO String
liftIO $ putStrLn $ "Hello, " ++ n -- MaybeT IO ()
_
mgreet
のすべてのステートメントは、_MaybeT IO
_モナドからのものです。
すべてのモナド変換子には「実行」機能があります。
Run関数は、モナドスタックの最上層を「実行」して、内側の層から値を返します。
_MaybeT IO
_の場合、実行関数は次のとおりです。
_runMaybeT :: MaybeT IO a -> IO (Maybe a)
_
例:
_ghci> :t runMaybeT mgreet
mgreet :: IO (Maybe ())
ghci> runMaybeT mgreet
What is your name? user5402
Hello, user5402
Just ()
_
また、実行してみてください:
_runMaybeT (forever mgreet)
_
ループから抜け出すには、Ctrl-Cを使用する必要があります。
これまでのところmgreet
は、IOで実行できること以上のことはしていません。次に、MaybeモナドとIOを混合することの威力を示す例に取り組みます。
いくつかの質問をするプログラムから始めます:
_ askfor :: String -> IO String
askfor Prompt = do
putStr $ "What is your " ++ Prompt ++ "? "
getLine
survey :: IO (String,String)
survey = do n <- askfor "name"
c <- askfor "favorite color"
return (n,c)
_
ここで、ユーザーが質問に応答してENDと入力することで、調査を早期に終了できるようにしたいとします。次のように処理します。
_ askfor1 :: String -> IO (Maybe String)
askfor1 Prompt = do
putStr $ "What is your " ++ Prompt ++ " (type END to quit)? "
r <- getLine
if r == "END"
then return Nothing
else return (Just r)
survey1 :: IO (Maybe (String, String))
survey1 = do
ma <- askfor1 "name"
case ma of
Nothing -> return Nothing
Just n -> do mc <- askfor1 "favorite color"
case mc of
Nothing -> return Nothing
Just c -> return (Just (n,c))
_
問題は、_survey1
_によくある階段の問題があり、さらに質問を追加しても拡大縮小されないことです。
ここでは、MaybeTモナド変換子を使用できます。
_ askfor2 :: String -> MaybeT IO String
askfor2 Prompt = do
liftIO $ putStr $ "What is your " ++ Prompt ++ " (type END to quit)? "
r <- liftIO getLine
if r == "END"
then MaybeT (return Nothing) -- has type: MaybeT IO String
else MaybeT (return (Just r)) -- has type: MaybeT IO String
_
_askfor2
_のすべてのステートメンが同じモナド型を持っていることに注意してください。
新しい関数を使用しました:
_MaybeT :: IO (Maybe a) -> MaybeT IO a
_
タイプがどのように機能するかを次に示します。
_ Nothing :: Maybe String
return Nothing :: IO (Maybe String)
MaybeT (return Nothing) :: MaybeT IO String
Just "foo" :: Maybe String
return (Just "foo") :: IO (Maybe String)
MaybeT (return (Just "foo")) :: MaybeT IO String
_
ここでreturn
はIOモナドからのものです。
これで、次のような調査関数を記述できます。
_ survey2 :: IO (Maybe (String,String))
survey2 =
runMaybeT $ do a <- askfor2 "name"
b <- askfor2 "favorite color"
return (a,b)
_
_survey2
_を実行して、いずれかの質問に対する応答としてENDと入力して、質問を早期に終了してください。
次のショートカットについて言及しないと、他の人からコメントが届くと思います。
表現:
_MaybeT (return (Just r)) -- return is from the IO monad
_
単に次のように書くこともできます:
_return r -- return is from the MaybeT IO monad
_
また、MaybeT (return Nothing)
を記述する別の方法は次のとおりです。
_mzero
_
さらに、2つの連続するliftIO
ステートメントは常に1つのliftIO
に結合される場合があります。例:
_do liftIO $ statement1
liftIO $ statement2
_
と同じです:
_liftIO $ do statement1
statement2
_
これらの変更により、_askfor2
_関数を作成できます。
_askfor2 Prompt = do
r <- liftIO $ do
putStr $ "What is your " ++ Prompt ++ " (type END to quit)?"
getLine
if r == "END"
then mzero -- break out of the monad
else return r -- continue, returning r
_
ある意味で、mzero
は、例外をスローするようなモナドから抜け出す方法になります。
この単純なパスワード要求ループについて考えてみましょう。
_loop1 = do putStr "Password:"
p <- getLine
if p == "SECRET"
then return ()
else loop1
_
これは(末尾の)再帰関数であり、正常に動作します。
従来の言語では、これをbreakステートメントを使用した無限のwhileループとして記述します。
_def loop():
while True:
p = raw_Prompt("Password: ")
if p == "SECRET":
break
_
MaybeTでは、Pythonコードと同じ方法でループを記述できます。
_loop2 :: IO (Maybe ())
loop2 = runMaybeT $
forever $
do liftIO $ putStr "Password: "
p <- liftIO $ getLine
if p == "SECRET"
then mzero -- break out of the loop
else return ()
_
最後のreturn ()
は実行を継続し、forever
ループに入っているため、制御はdoブロックの先頭に戻ります。 _loop2
_が返すことができる唯一の値はNothing
であり、これはループから抜け出すことに対応しています。
状況によっては、再帰的な_loop2
_よりも_loop1
_を書く方が簡単な場合があります。
foo :: IO (Maybe a)
、func1 :: a -> IO (Maybe b)
、func2 :: b -> IO (Maybe c)
など、ある意味で「失敗する可能性がある」IO
値を処理する必要があるとします。
バインドのチェーンにエラーの存在を手動でチェックすると、恐ろしい「運命の階段」がすばやく生成されます。
_do
ma <- foo
case ma of
Nothing -> return Nothing
Just a -> do
mb <- func1 a
case mb of
Nothing -> return Nothing
Just b -> func2 b
_
これを何らかの方法で「自動化」する方法は?おそらく、最初の引数がNothing
内のIO
かどうかを自動的にチェックするバインド関数を使用して、IO (Maybe a)
の周りに新しいタイプを考案し、自分でチェックする手間を省くことができます。何かのようなもの
_newtype MaybeOverIO a = MaybeOverIO { runMaybeOverIO :: IO (Maybe a) }
_
バインド機能で:
_betterBind :: MaybeOverIO a -> (a -> MaybeOverIO b) -> MaybeOverIO b
betterBind mia mf = MaybeOverIO $ do
ma <- runMaybeOverIO mia
case ma of
Nothing -> return Nothing
Just a -> runMaybeOverIO (mf a)
_
これでうまくいきます!さらに詳しく見てみると、IO
モナド専用の特定の関数を使用していないことがわかります。 newtypeを少し一般化すると、基礎となるすべてのモナドでこれを機能させることができます。
_newtype MaybeOverM m a = MaybeOverM { runMaybeOverM :: m (Maybe a) }
_
そして、これは、本質的に、MaybeT
トランスフォーマー 機能する です。トランスフォーマーにreturn
を実装する方法、IO
値を_MaybeOverM IO
_値に「リフト」する方法など、いくつかの詳細は省略しました。
MaybeOverIO
の種類は_* -> *
_ですが、MaybeOverM
の種類は_(* -> *) -> * -> *
_です(最初の「型引数」はモナド型コンストラクタであるため、「型引数」が必要です。 ")。
確かに、MaybeT
モナド変換子は次のとおりです。
newtype MaybeT m a = MaybeT {unMaybeT :: m (Maybe a)}
そのモナドインスタンスを次のように実装できます。
instance (Monad m) => Monad (MaybeT m) where
return a = MaybeT (return (Just a))
(MaybeT mmv) >>= f = MaybeT $ do
mv <- mmv
case mv of
Nothing -> return Nothing
Just a -> unMaybeT (f a)
これにより、特定の状況で正常に失敗するオプションを使用してIOを実行できます。
たとえば、次のような関数があるとします。
getDatabaseResult :: String -> IO (Maybe String)
その関数の結果でモナドを個別に操作できますが、そのように構成すると、次のようになります。
MaybeT . getDatabaseResult :: String -> MaybeT IO String
その余分なモナド層を忘れて、通常のモナドとして扱うだけです。