型シグネチャは、関数が純粋であることを示しています (つまり、Int を取り、String を返します) が、内部では IO を実行しています! Haskell では、そのような関数を作成することはできません。ファイルから読み取ったものはすべて IO モナドに永久にスタックされます。それで終わりです (もちろん、安全でない関数は除きます)。
この場合、Yesod は非常に IO ベースのフレームワークであるため、それほど悪くはありません。すべてのネットワーク トラフィックも IO モナドでスタックします。
モナド変換子スタックにいる場合、スタックの各レベルでモナド計算にアクセスできますが、直接アクセスできるのはそのうちの 1 つだけです。lift
計算をモナドからスタック内の 1 層下の変換されたモナドに移動するために使用します。がスタック内にある場合IO
は、下のレイヤーがいくつあっても、 を介してそのアクションに直接アクセスできますliftIO
。
したがって、持っているtype T = ReaderT String IO
場合は、機能がある可能性がありますfoo :: Int -> T String
。この関数では、モナド機能でモナドT
を変換するIO
モナドで操作しReader
ます。このコンテキストでは、結果lift readFile
を得る代わりに、IO String
結果を得ることができますT String
。ただし、これは単にIO String
ラップされた型なので、モナドReaderT
をエスケープするようなトリッキーなことをしたとは思わないでください。IO
少しわかりにくいかもしれないので、例を見てみましょう。
import Control.Monad.Reader (ReaderT)
import Control.Monad.Writer (WriterT)
import Control.Monad.Trans (lift, liftIO)
type T = ReaderT String IO
getSortedMiddleElement :: Int -> IO String
foo :: Int -> T String
foo n = do
str <- lift $ getSortedMiddleElement n --str holds a pure String now
lift $ putStrLn str --get `putStrLn` from IO and pass the String
return str --let's wrap it back in T now
しかし、IO から 1 層以上離れている場合はどうなるでしょうか。試してみましょう:
type W = WriterT String T -- WriterT String (ReaderT String IO)
-- This doesn't work; lift only gives you access to the next layer's actions
-- but IO is now more than one layer away!
--
--bar n = do
-- str <- lift $ getSortedMiddleElement n
-- Instead, we need liftIO, which will access IO across many transformer layers
bar :: Int -> W String
bar n = do
str <- liftIO $ getSortedMiddleElement n
liftIO $ putStrLn str
return str