The Haskell 98 Report
top | back | next | contents | function index


21  入出力


module IO (
    Handle, HandlePosn,
    IOMode(ReadMode,WriteMode,AppendMode,ReadWriteMode),
    BufferMode(NoBuffering,LineBuffering,BlockBuffering),
    SeekMode(AbsoluteSeek,RelativeSeek,SeekFromEnd),
    stdin, stdout, stderr, 
    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_,

    -- ...and what the Prelude exports
    IO, FilePath, IOError, ioError, userError, catch, interact,
    putChar, putStr, putStrLn, print, getChar, getLine, getContents,
    readFile, writeFile, appendFile, readIO, readLn
    ) where

import Ix(Ix)

data Handle = ... -- implementation-dependent
instance Eq Handle where ...
instance Show Handle where ..           -- implementation-dependent

data HandlePosn = ... -- implementation-dependent
instance Eq HandlePosn where ...
instance Show HandlePosn where ---      -- implementation-dependent


data IOMode      =  ReadMode | WriteMode | AppendMode | ReadWriteMode
                    deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)
data BufferMode  =  NoBuffering | LineBuffering 
                 |  BlockBuffering (Maybe Int)
                    deriving (Eq, Ord, Read, Show)
data SeekMode    =  AbsoluteSeek | RelativeSeek | SeekFromEnd
                    deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)

stdin, stdout, stderr :: Handle

openFile              :: FilePath -> IOMode -> IO Handle
hClose                :: Handle -> IO ()


hFileSize             :: Handle -> IO Integer
hIsEOF                :: Handle -> IO Bool
isEOF                 :: IO Bool
isEOF                 =  hIsEOF stdin

hSetBuffering         :: Handle  -> BufferMode -> IO ()
hGetBuffering         :: Handle  -> IO BufferMode
hFlush                :: Handle -> IO () 
hGetPosn              :: Handle -> IO HandlePosn
hSetPosn              :: HandlePosn -> IO () 
hSeek                 :: Handle -> SeekMode -> Integer -> IO () 

hWaitForInput       :: Handle -> Int -> IO Bool
hReady                :: Handle -> IO Bool 
hReady h       =  hWaitForInput h 0
hGetChar              :: Handle -> IO Char
hGetLine              :: Handle -> IO String
hLookAhead            :: Handle -> IO Char
hGetContents          :: Handle -> IO String
hPutChar              :: Handle -> Char -> IO ()
hPutStr               :: Handle -> String -> IO ()
hPutStrLn             :: Handle -> String -> IO ()
hPrint                :: Show a => Handle -> a -> IO ()

hIsOpen               :: Handle -> IO Bool
hIsClosed             :: Handle -> IO Bool
hIsReadable           :: Handle -> IO Bool
hIsWritable           :: Handle -> IO Bool
hIsSeekable           :: Handle -> IO Bool

isAlreadyExistsError  :: IOError -> Bool
isDoesNotExistError   :: IOError -> Bool
isAlreadyInUseError   :: IOError -> Bool
isFullError           :: IOError -> Bool
isEOFError            :: IOError -> Bool
isIllegalOperation    :: IOError -> Bool
isPermissionError     :: IOError -> Bool
isUserError           :: IOError -> Bool

ioeGetErrorString     :: IOError -> String
ioeGetHandle          :: IOError -> Maybe Handle
ioeGetFileName        :: IOError -> Maybe FilePath

try                   :: IO a -> IO (Either IOError a)
bracket               :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket_              :: IO a -> (a -> IO b) -> IO c -> IO c

Haskell において用いられるモナド I/O システムについては、Haskell 言語レポートで記述されている。print のように一般によく 使われている I/O 関数は標準 Prelude の一部になっており、明示的に インポートする必要はない。このライブラリはより高度な I/O を 含んでいる。ファイルシステム上の関連操作のいくつかは Directory ライブラリに含まれている。

21.1  I/O エラー

IOError のエラーは I/O モナドで使われる。 これは抽象型である。このライブラリでは IOError のなかの値を 探ったり、そのなかに値を構築したりするための関数が供給されている。

これらの関数はどれも Bool 値を返す。その引数が対応する エラーであった場合には True を、さもなければ、 False を返す。

IO 型の結果を返す計算はどれも isIllegalOperationError で失敗する可能性がある。実装によって引き起こされうるエラーは 対応する操作の後にリストアップしてある。場合によっては、実装が どのエラーが起ったかを判別できないこともある。その場合には isIllegalOperationError のものを返さなければならない。

さらに、エラー値に関する情報を得るための関数が3つ用意されている。 ioeGetHandle はエラー値がハンドル hdl を 参照していれば、Just hdl を返し、さもなければ、 Nothing を返す。ioeGetFileName はエラー値が ファイル name を参照していれば、Just name を返し、さもなければ、Nothing を返す。 ioeGetErrorString は文字列を返す。(fail を用いて 引き起こされた)利用者定義のエラーについては、 ioeGetErrorString によって返される文字列が、fail に渡された引数である。その他のエラーについての文字列は実装依存である。

try 関数は計算のなかで明示的に Either 型を使って、 エラーを返す。

bracket 関数は、計算途中のエラーの場合でも、メモリ解放が 必要になるような、よくあるメモリ確保、計算、メモリ解放の定型句を 捕捉する。これは Java の try-catch-finally の機構に類似している。

21.2  ファイルとハンドル

Haskell の外界とのインタフェースは抽象的なファイルシステム である。ここでいうファイルシステムとは名前の付いた ファイルシステムオブジェクトの集りであり、それらは、 ディレクトリにまとめられるものである (Directory を見よ)。実装によってはディレクトリ自身が ファイルシステムオブジェクトであり、また、別のディレクトリの エントリである場合もある。単純化するために、すべてのディレクトリでは ないファイルシステムオブジェクトをファイル呼ぶことにしよう。 実際には通信チャネルであったり、オペレーティングシステム上では 別のものとして認識されることはあるけれども。物理ファイル は永続的で、順序付けられたファイルのことで、一般にはディスク上に 置かれている。

ファイルとディレクトリの名前は String 型の値である。 その正確な意味はオペレーティングシステムに依存する。ファイルは オープンすることが可能で、その結果はハンドルであり、 これを使ってファイルの内容を操作することができる。

Haskell ではファイルからの文字の読み出し、ファイルへの 文字の書き込み操作が定義されており、これは Handle 型の値で あらわされる。この型のそれぞれの値はハンドル、 すなわち Haskell の実行時システムがファイルシステムオブジェクトの 入出力を管理するために使うものである。ハンドルは少なくとも 以下の特性をもつ。

ほとんどの場合ハンドルは次の入出力操作がどこで起こるかを示す 現在の入出力位置というものをもつ。ハンドルはもし入力だけ、あるいは 入出力の両方を管理できるのであれば読み込み可能である。 同様に、もし出力だけあるいは入出力の両方が管理できるのであれば 書きこみ可能である。ハンドルは最初にアロケートされたときに オープンされている。一旦クローズされるとそれ移行、入力、出力ともに 使えなくなる。これは、ハンドルへのリファレンスがたとえ残っていても、 実装はそのストーレジを再利用できないということである。 ハンドルは Show および Eq クラスである。 ハンドル表示のための文字列はシステム依存である。その文字列には デバッグ用にハンドルを識別するのに十分な情報を含んでいる。 ハンドルは、== に関して、自分自身でとのみ等しい。 同値性に関して、別のハンドルと内部状態を比較するようなことはない。

21.2.1  標準ハンドル

3 つのハンドルがプログラムの初期化中にアロケートされる。最初の二つ (stdinstdout)はそれぞれ Haskell のプログラム での標準入力チャネルと標準出力チャネルを管理するものである。 3 つめのもの(stderr)は標準エラーチャネルを管理するものである。 この 3 つのハンドルは最初からオープンされている。

21.2.2  ハンドルのセミクローズ

操作 hGetContents hdl (21.9.4 節)はハンドル hdl を中間状態セミクローズ状態にする。この状態では、hdl は実質的にはクローズ状態であるが、hdl から必要に応じて 読み込みをおこない、hGetContents hdl によって 返される特別なリストに読み込んだものが蓄積される。

すべての操作はハンドルがクローズされていると失敗する。同様に ハンドルがセミクローズされていても失敗する。唯一の例外は、 hClose である。セミクローズされたハンドルは以下の場合に クローズされる。

セミクローズ状態のハンドルが一旦クローズされてしまうと、対応する リストの内容は固定される。この最終的なリストの内容は部分的に規定 されているにすぎない。少くとも、ハンドルがクローズされるに先だって 評価されたストリームのデータを含むことになる。

ハンドルがセミクローズ状態にある場合に起きたあらゆる I/O エラー は単純に廃棄される。

21.2.3  ファイルのロック

実装ではできうるかぎり、Haskell のプロセス内では、ファイルに対しては 読み込み多重、書きこみは 1 つだけというロック機構を実現しなければ ならない。したがって、同じファイルに対して入力管理用に複数の ハンドルが存在する可能性があり、出力管理用には一つのファイルには 一つのハンドルしか存在しない。もし、出力用にハンドルがオープン された状態あるいはセミクローズの状態である場合には、そのファイルに 対してはハンドルをアロケートすることだけはできるが、 そのハンドルで出力を管理することはできない。二つのファイルが同じで あるかどうかは実装依存であるが、たとえば、ふつう、同じ絶対パス名を もち、リネームされていなければ、同じであるべきである。

警告: readFile 操作 (7.1 節)は、 当該ファイルのすべてのコンテンツが消費されてしまわないうちは セミクローズされたハンドルを保持する。その結果として、先に readFile でオープンされたファイルに(たとえば、 writeFile を使って)書き込もうとすると通常、 isAlreadyInUseError となって失敗することになるだろ。

21.3  ファイルのオープンとクローズ

21.3.1  ファイルのオープン

計算 openFile file mode はファイル file を管理するための新しいオープンされたハンドルをアロケートしそれを返す。 このハンドルは modeReadMode であれば入力用であり、 modeWriteMode あるいは AppendMode であれば出力用であり、modeReadWriteMode ならば 入出力両用である。

もし、ファイルが存在せず、出力用にオープンされれば新にファイルが 作成される。もし、modeWriteMode でファイルが すでに存在していれば、そのファイルは長さゼロに丸められる。 オペレーティングシステムによっては空のファイルを消去するものも あるので、ファイルが mode WriteModeopenFile された後に書きこみが成功しなかった場合、 そのファイルが相変わらず存在していることは保証されない。 ファイルがもし、AppendMode でオープンされた場合には ハンドルの位置はファイルの最後にあり、それ以外の場合は先頭 (この場合には内部的な I/O 位置が 0 )にある。初期のバッファモードは 実装依存である。

もし、openFile が出力ファイルのオープンに失敗した場合でも、 そのファイルが存在しなければ、新たに作成される。

エラー報告:openFile の計算はもしファイルが 既にオープンされていて再オープンできなければ、 isAlreadyInUseError となって失敗し、ファイルが存在しなければ isDoesNotExistError、ファイルをオープンする権限を ユーザがもたなければ、isPermissionErrorとなる。

21.3.2  ファイルのクローズ

hClose hdl という計算はハンドル hdl を クローズする。この計算が終了する前に、もし、hdl が書きこみ 可能であれば、そのバッファは hFlush を使うのと同様に フラッシュされる。hClose をすでにクローズされたハンドルに 実行しても、何もおこらず、エラーにもならない。クローズされた ハンドルへのそれ以外の操作は失敗する。もし、hCloseが、 なんらかの理由で失敗したら、それ以上の(hClose以外の) オペレーションは hdl が正しくクローズされた場合と同様に 失敗する。

21.4  ファイルサイズの決定

物理的なファイルに結び付けられたハンドル hdl に対して、 hFileSize hdl はファイルの大きさを 8-bit バイトで返す (>= 0)。

21.5  入力終端の検出

読み出し可能なハンドル hdl に対し、計算 hIsEOF hdl は、hdl からこれ以上入力を取り出せない場合には True を返す。このことは、物理的なファイルに結び付けられた ハンドルについて言えば、現在の入出力位置がそのファイルの長さに等しい ということである。もし、終端でなければ、False が返る。 計算 isEOFstdin 上でのみ動作するということを 除けば、同一の計算である。

21.6  バッファリング操作

3 種類のバッファリングがサポートされている。行バッファリング、 ブロックバッファリング、バッファリングなし、である。これらのモードは 以下の効果がある。出力に関しては、項目は内部バッファからその バッファモードに応じて書き出されるか、あるいはフラッシュされる

実装は上での規定よりも頻繁にバッファをフラッシュしてもよい。しかし、 上の規定以下の頻度にしてはならない。バッファは書き出しが済むとすぐに 空にされる。

同様にして、入力はそのハンドル hdl のバッファモードに したがって起こる。

大多数の実装では、物理的なファイルはふつうブロックバッファリング モードで操作し、ターミナルは行バッファリングモードで操作する。

計算 hSetBuffering hdl mode はハンドル hdl のその後の読み書きのバッファリングモードを設定する。

もし、バッファモードが BlockBuffering または、 LineBuffering から NoBuffering へ変更された場合、

エラー報告:もし、ハンドルがすでに読み込み、あるいは 書き出し用に使用されていて、かつ、バッファリングモードの変更を 許さないように実装されている場合には、hSetBuffering 計算は isPermissionError となり失敗する。

計算 hGetBuffering hdlhdl の現在の バッファリングモードを返す。

ハンドルがオープンされたときのデフォルトのバッファリングモードは 実装依存であり、ハンドルが割り当てられたファイルシステムオブジェクト に依存する。

21.6.1  バッファのフラッシュ

計算 hFlush hdl はハンドル hdl の 出力バッファの全ての内容を直ちにオペレーティングシステムへ送りこむ。

エラー報告:もし、デバイスがフルであれば hFlush 計算は isFullError となり、システムリソースの制限を 超えた場合は isPermissionError となって失敗する。 バッファ内の文字が破棄されるか、そのままの状態になるかは規定されていない。

21.7  ハンドルの再配置

21.7.1  I/O 位置の再訪

計算 hGetPosn hdlhdl の現在の I/O 位置を 抽象データ型 HandlePosn の値として返す。もし、 hGetPosn h の呼び出しが位置 p を返したら、 計算hSetPosn ph の位置を hGetPosn が呼び出された時の位置に設定する。

エラー報告:もし、システムリソースの制限を超えたら、計算 hSetPosnisPermissionError となり失敗する。

21.7.2  新しい位置へのシーク

計算 hSeek hdl mode i はハンドル hdl の位置を mode にしたがって設定する。 もし mode

オフセットは 8-bit バイトで与えられる。

もし、hdl がブロックバッファリングモードあるいはライン バッファリングモードなら、現在のバッファ内にない位置へのシークを 行うと、まず、出力バッファ内にあるデータをすべてデバイスに 書き出し、そのあと、入力バッファを破棄する。一部のハンドルは シーク不可能な場合がある( hIsSeekableの項を見よ)。または、 可能な位置どり操作のみをサポートする場合(たとえば、テープの最後に のみ位置どりをすることが可能であるとか、先頭の位置あるいは現在 位置からのオフセットでのみ位置どりができるということ)もある。 負のI/O位置を設定するのは不可能である。また、物理的なファイルに 対して現在の終端を超える位置どりも不可能である。

エラー報告: hSeek 計算はシステムの資源限界を 超えると isPerissionError で失敗する。

21.8  ハンドルの性質

関数 hIsOpenhIsClosedhIsReadablehIsWritable および hIsSeekable はハンドルの性質に関する情報を返す。これらの関数はハンドルが、 指定の性質をもつなら True を返し、そうでなければ、 False を返す。

21.9  テキスト入出力

ここで、テキストファイルからハンドルを用いて文字あるいは文字列を 読み出すための標準の入力操作群を定義する。これらの関数の多くは プレリュードにある関数の一般化したものである。プレリュードのI/O関数は ほとんどが、stdin および stdout を使うものであるが、 ここでは、ハンドルが明示的にI/O操作によって指定されている。

21.9.1  入力のためのチェック

hWaitForInput hdl t の計算はハンドル hdl 上で入力が利用可能になるのを待ち合わせる。この関数はハンドル hdl 上で入力が利用可能になりしだい True を返す。もし t ミリ 秒以内に入力が利用可能にならなければ、False を返す。

hReady hdl の計算はすなくとも 1 つの項目がハンドル hdl で利用可能になっているかどうかを表示する。

エラー報告: hWaitForInputhReady の計算は、 ファイルの終端に到達していると isEOFError で失敗する。

21.9.2  入力の読み込み

hGetChar hdl 計算は hdl により管理されている ファイルあるいはチャネルから一文字読み出す。

hGetLine hdl 計算は hdl により管理されている ファイルあるいはチャネルから一行読み出す。プレリュード中の getLinehGetLine stdin との省略形である。

エラー報告: hGetContents 計算は、ファイルの終端に 到達していれば、isEOFError で失敗する。hGetLine 計算は、行の最初の文字を読んだときに、ファイル終端に出会えば、 isEOFError で失敗する。hGetLine がそれ以外の 場所でファイル終端に出会えば、それを行の終端として扱い、その (部分)行を返す。

21.9.3  先読み

hLookAhead hdl の計算はハンドル hdl から 次の一文字を読み出すが、それを入力バッファから取り除かない。また、 一文字利用可能になるまで、ブロックする。

エラー報告: hLookahead 計算は、ファイルの終端に到達していると isEOFError で失敗する。

21.9.4  入力全体の読み込み

hGetContents hdl 計算は hdl により 管理されているファイルあるいはチャネルの未読部分にある文字の リストを返し、ハンドルをセミクローズする。

エラー報告: hGetContents 計算は、ファイルの終端に到達していれば、 isEOFError で失敗する。

21.9.5  テキスト出力

hPutChar hdl c 計算は文字 chdl で管理されているファイルあるいはチャネルに書き込む。hdl に対して バッファリングが可能になっていれば、文字はバッファに蓄えられる。

hPutStr hdl s 計算は文字列 shdl で管理されているファイルあるいはチャネルに書き込む。

hPrint hdl t 計算は shows 関数で あたえられる t を表現する文字列をhdl で管理されているファイルあるいはチャネルに書き込み、改行を追加する。

エラー報告: hPutCharhPutStr および hPrint の 計算はデバイスがフルであれば isFull-Error で失敗し、 別のシステム資源の制限を超えると isPermissionError で失敗する。

21.10  

Haskell の入出力を説明するためにいくつか例をあげよう。

21.10.1  二つの数の和

このプログラムは読み込んだ二つの Integerの和を計算する

import IO

main = do
         hSetBuffering stdout NoBuffering            
         putStr   "Enter an integer: "        
         x1 <- readNum 
         putStr   "Enter another integer: "          
         x2 <- readNum                          
         putStr  ("Their sum is " ++ show (x1+x2) ++ "\n")
       where readNum :: IO Integer
-- Providing a type signature avoids reliance on
-- the defaulting rule to fix the type of x1,x2
             readNum = readLn

21.10.2  ファイルのコピー

ファイルの複製を作る単純なプログラムで、すべての小文字は大文字に 変換される。このプログラムは自分自身へのコピーは許さない。 このバージョンでは文字レベルの入出力を使う。必ず、プログラムへは 引数を二つ与えなければならないことに注意せよ。

import IO
import System
import Char( toUpper )

main = do 
         [f1,f2] <- getArgs
         h1 <- openFile f1 ReadMode     
         h2 <- openFile f2 WriteMode 
         copyFile h1 h2            
         hClose h1                  
         hClose h2

copyFile h1 h2 = do
                   eof <- hIsEOF h1
                   if eof then return () else
                      do
                        c <- hGetChar h1
                        hPutChar h2 (toUpper c)   
                        copyFile h1 h2

文字列の入出力を用いた同等のずっと短いバージョンは以下のとおり。

import System
import Char( toUpper )

main = do
         [f1,f2] <- getArgs
         s <- readFile f1
         writeFile f2 (map toUpper s)

21.11  IO ライブラリ

module IO {- export list omitted -} where

-- Just provide an implementation of the system-independent
-- actions that IO exports.

try            :: IO a -> IO (Either IOError a)
try f          =  catch (do r <- f
                            return (Right r))
                        (return . Left)

bracket        :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket before after m = do
        x  <- before
        rs <- try (m x)
        after x
        case rs of
           Right r -> return r
           Left  e -> ioError e

-- variant of the above where middle computation doesn't want x
bracket_        :: IO a -> (a -> IO b) -> IO c -> IO c
bracket_ before after m = do
         x  <- before
         rs <- try m
         after x
         case rs of
            Right r -> return r
            Left  e -> ioError e


The Haskell 98 Report
top | back | next | contents | function index
December 2002