HOME Haskell のお勉強 書き込む

5. 入出力


Haskell は純粋な関数型言語なので入出力のような副作用のある 操作は少しめんどくさくなります。

しかし、このことによって、副作用のある部分を副作用の無い関数から 隔離することが出来ます。

1. do 記法

do ブロックの中の式は順番に実行されます。 IO は、入力 → 計算 → 出力 の順番で実行する 必要があるので do 記法を用いて記述します。

do 記法の代わりに >>= を使うことも出来ます。 この方法は Monad のところで説明します。 do ブロックは通常の関数と異なるのでアクションと呼ばれます。 do ブロックから外部に値を返すには return を用います。 return a とすると外部には IO a が返ります。 (正確な説明は Monad のところでします。)

ファイル読み込みなどの入力アクションが返す値は IO a と呼ばれ、 普通の a と区別されます。 IO a を a に換えるには、do ブロックの中で <- を使います。 <- の厳密な定義は モナドのところで説明します。

2. 標準入出力

getChar, getLine, getContentsIO String を返すアクションなので 返り値を普通の String にするには <- を用います。
-- simple echo 
-- wrong, an IO String is not a String
putStrLn $ getLine

-- ok
do str <- getLine  -- converting IO String into String and 'bind' it to str
   putStrLn str

-- also ok
getLine >>= putStrLn
以下に user account と password を聞き、正しければ log in したと表示する アクション login を示します。
login = do putStr "Enter user name: "
           user <- getLine
           putStr "Enter password: "
           pass <- getLine
           if is_user user pass
               then putStrLn $ user ++ " has logged in"
               else putStrLn "Permission denied."
 where is_user "Taro" "baseball" = True
       is_user "Hanako" "swimming" = True
       is_user _ _                 = False

3. ファイルの読み書き

3.1. Prelude に定義されている基本的なファイル IO アクション

基本的なファイル IO アクションを次の表に示します。 通常はこれらのアクションで間に合います。
(そもそもこれで間に合わないことは Haskell ではなく Python などの他のプログラミング言語を用いた方が良い。)
アクション名 説明
writeFile FilePath -> String -> IO () FilePath に String を書きます。
appendFile FilePath -> String -> IO () FilePath に String を追加します。
readFile FilePath -> IO String FilePath から IO String を読みます。

例:file copy

copy_file from to = do contents <- readFile from
                       writeFile to contents

3.2. module IO に定義されているアクション

module IO にあるアクションを使えばより精密に IO を扱えます。

module IO で定義されているアクションは

openFile, hClose, hFileSize, hIsEOF, isEOF, hSetBuffering, hGetBuffering, hFlush, hGetPosn, hSetPosn, hSeek, hWaitForInput, hReady, hGetChar, hGetLine, hLookAhead, hGetContents, hPutChar, hPutStr, hPutStrLn, hPrint, hIsOpen, hIsClosed, hIsReadable, hIsWritable, hIsSeekable, isAlreadyExistsError, isDoesNotExistError, isAlreadyInUseError, isFullError, isEOFError, isIllegalOperation, isPermissionError, isUserError, ioeGetErrorString, ioeGetHandle, ioeGetFileName, try, bracket, bracket
などがあります。 詳しくは The Haskell 98 Report, 21 Input/Output を見てください。

例:ファイルの最初の n 行を表示する。(UNIX コマンドの head に相当)
はじめの n 行を表示させたいときは、ファイルの内容全てを読むことは 無駄なので、n 行だけ読むようにします。

01:     import IO
02:     
03:     fhead n filename = bracket (openFile filename ReadMode)
04:                                hClose
05:                                (\h -> do hSetBuffering h LineBuffering
06:                                          take_line 0 h)
07:      where take_line i h' | i == n = return ()
08:                           | otherwise = do str <- hGetLine h'
09:                                            putStrLn str
10:                                            take_line (i+1) h' 

説明:
説明
01 module IO を import します。
03 表示する行数とファイル名を引数として取るアクション fhead を定義します。
03-06 bracket は 3 つの引数を取ります。
  1. 最初に行うアクション。
  2. 最後に必ず行うアクション。
  3. 最初のアクションに続いて行うアクション。
    このアクションの引数は最初のアクションの戻り値(この場合はファイルハンドル)です。
05 IO buffer のサイズを LineBuffering にします。
06 take_line はファイルハンドルから n 行読むアクションです。(7行目で定義)
07--10 take_line の定義です。
07 n 行読み込んだら IO () を返します。
08 そうでなければ、ファイルハンドルから 1 行読み込んで、
09 標準出力に吐き出して、
10 次の行を読み込んで表示します。(take_line を再帰)

4. コマンドラインの引数

コマンドラインの引数は getArgs を使って取得します。
01:     -- av.hs
02:     -- just show command line arguments
03:     module Main where
04:     import System
05:     
06:     main = do putStrLn "The arguments are:"
07:               av <- getArgs
08:               putStr $ unlines av
D:\doc\05-03\haskell>runhugs av.hs hi how are you ?
The arguments are:
hi
how
are
you
?

5. より詳しく知るには