Drkcore

15 06 2012 Haskell Tweet

モナド変換子の使い方がわかってきた

モナドの上にまたモナドって株か?二階建てか?なんて思って敬遠してたのだけど、なんとなくわかってくると、包んで包んでしてるだけじゃないかと。

RWH18章

モナド変換子を使わないでログ記録。これはIO [(FilePath, Int)]型になる。

module CountEntries (listDirectory, countEntriesTrad) where

import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.FilePath ((</>))
import Control.Monad (forM, liftM, when)

listDirectory :: FilePath -> IO [String]
listDirectory = liftM (filter notDots) . getDirectoryContents
    where notDots p = p /= "." && p /= ".."

countEntriesTrad ::FilePath -> IO [(FilePath, Int)]
countEntriesTrad path = do
  contents <- listDirectory path
  rest <- forM contents $ \name -> do
                          let newName = path </> name
                          isDir <- doesDirectoryExist newName
                          -- when isDir $ countEntriesTrad newName
                          if isDir
                             then countEntriesTrad newName
                             else return []
  return $ (path, length contents) : concat rest

続いてモナド変換子(WriterT)を使う。WriterT [(FilePath, Int)] IO () という型は一見威圧されて目を背けたくなるところだが、単にIO箱を更にWriterT箱に包みつつそっちの箱に記録を取っていると考えればいいだけ。

元のcountEntriesTradと新しいcountEntriesを見比べてみるとliftIOっていうIOの箱の梱包を解いて中身を取り出す関数が増えているくらいだというのがわかる。

module CountEntriesT (listDirectory, countEntries) where

import CountEntries (listDirectory)
import System.Directory (doesDirectoryExist)
import System.FilePath ((</>))
import Control.Monad (forM_, when)
import Control.Monad.Trans (liftIO)
import Control.Monad.Writer (WriterT, tell)

countEntries ::FilePath -> WriterT [(FilePath, Int)] IO ()
countEntries path = do
  contents <- liftIO . listDirectory $ path
  tell [(path, length contents)]
  forM_ contents $ \name -> do
                          let newName = path </> name
                          isDir <- liftIO . doesDirectoryExist $ newName
                          when isDir $ countEntries newName

countEntriesでwhenを使っていたのでcountEntriesTradでも使えるかなと思ったんだけど、型が合わなくて無理だった。ソース見ると

when              :: (Monad m) => Bool -> m () -> m ()
when p s          =  if p then s else return ()

-- | The reverse of 'when'.

unless            :: (Monad m) => Bool -> m () -> m ()
unless p s        =  if p then return () else s

と定義されているのでIO ()型の時しか使えないのね。

ProductName Real World Haskell―実戦で学ぶ関数型言語プログラミング
Bryan O'Sullivan
オライリージャパン / 3990円 ( 2009-10-26 )


おまけ

IO剥がすのになんでliftIOって言うんだろ?liftっていうのが自分の直感的なイメージに反するのだけど。

About

  • もう5年目(wishlistありマス♡)
  • 最近はPythonとDeepLearning
  • 日本酒自粛中
  • ドラムンベースからミニマルまで
  • ポケモンGOゆるめ

Tag

Python Deep Learning javascript chemoinformatics Emacs sake and more...

Ad

© kzfm 2003-2021