ローンパターン
継続を用いて、リソースなどの寿命をコントロールするテクニック。withFileはその筆頭だ。 code:haskell
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile name mode = bracket (openFile name mode) hClose
直接リソースを解放する関数を呼び出すのに比べ、この方式にはいくつかの利点がある。
解放を保証する: 継続が終われば必ず解放される。
例外処理との相性が良い: bracket関数やfinally関数を使えば、アクションが失敗しても解放処理を必ず走らせることができる。
有用だが、いくつかの制約もある。
unliftioなどを活用すればReaderT上でも利用できるが、StateTのような状態を持ったモナドでは例外処理に対応できない。 最後に取得したリソースを最初に解放するという順序が強制される
(わざとやらない限り大丈夫だとは思うが)リソースを外部にリターンする事は禁止されておらず、厳密に言うと安全でない操作が可能である。ST と同様に、Rank2多相を使ってリソースの流出を防ぐ手はあるが「そこまでやる必要はない」という認識が一般的。
なお、このような関数を複数組み合わせると、ネストが深くなってしまうのではという懸念があるが、ContTによって簡単に回避できる。
code:haskell
import Control.Monad.Trans.Cont
evalContT $ do
foo <- ContT $ withFile "foo.txt" ReadMode
bar <- ContT $ withFile "bar.txt" AppendMode
...
ローンパターンはデータの送受信にも応用できる。送信をContT r IO、受信をIOアクションに分ければ、一度に複数のデータを送れるだけでなく、受信に失敗したときの例外処理も自然にこなせる。このテクニックは、waiやfranzで応用されている。 code:haskell
fetch :: Params -> ContT r IO (IO Response)
二つのApplicativeを合成する構造Composeを使えば、送信部分と受信部分を別々に結合するのも簡単だ。
code:haskell
getResult <- getCompose $ (,) <$> Compose (fetch ...) <*> Compose (fetch ...)
result <- getResult