<   2011年 09月 ( 33 )   > この月の画像一覧

IO モナドとの付き合い方(4)

IO モナドのプログラミングを行う上でのルールの最後は、IO モナドにどのようにして変数を導入するかだ。関数型プログラミング言語といっても、変数を全く使えないのは不便だ。IO モナドでも変数を使えるようにするにはどうすればいいのだろうか。

例えば次の do 記法のプログラムでは、変数 cs に getLine の戻値を代入できる。そうして、その変数の値をputStrLn で利用することができる。こういう処理ができないと書けないプログラムは多い。

main = do
  cs <- getLine
  putStrLn cs

IO モナド型関数を >>= 演算子でつないでいくというプログラムに、この変数への代入を導入するにはどうすればいいのだろうか。それは、>>= 演算子の右辺の関数を、ラムダ記法で記述すればいいのだ。この考え方を使うと、上のプログラムは次のように書くことができる。

getLine >>= (\cs -> putStrLn cs)

2個以上の変数を利用するときは、関数のカッコを入れ子にすることで対応できる。次の実行例では、as に代入した文字列と bs に代入した文字列をつなげて表示できる。

Prelude> getLine >>= (\as -> (getLine >>= (\bs -> putStrLn (as ++ bs))))
hello,
world
hello, world

ちょっと Scheme 風のプログラムになってしまったが、確かに変数 as と bs に文字列を代入して利用することができることが分かる。

したがって、IO モナドをプログラムするときの3番目のルールは次のようになる。
ルール3: 変数を使いたいときは >>= 演算子の右辺の式をラムダ記法で記述する。
もういちど、今までに述べたIOモナドのルールをまとめて見てみよう。
ルール1: IOモナドのプログラムは、IOモナド型の関数を >>= 演算子で結合した、IOモナド型の関数の連鎖である。IOモナド型の関数とは f : a -> IO b のように引数が1個で戻り値が IO b 型の関数である。

ルール2: >>= 演算子の左辺からの戻値が無いときは >> 演算子を使う。

ルール3: 変数を使いたいときは >>= 演算子の右辺の式をラムダ記法で記述する。
最も基本的なルールはルール1で、ルール2とルール3はルール1のバリエーションでしかない。何れにしてもこの3つの簡単なルールに従うだけでエラーのない IO モナドプログラムが簡単に作れるようになる。

もちろん、長いプログラムを書くのは do 記法でないと見通しが悪いが、3つのルールで IO モナドのプログラムを作り慣れたあとは、do 記法で現れたエラーにどういう意味があるのか、どう対策すればエラーを無くすことができるかが簡単にわかるようになる。

IO モナドは決して Haskell の鬼門などではない。本来の方法でプログラムすれば、非常に簡潔なルールで純粋関数から隔離された、頑強な入出力のプログラムが書けることが分かる。

結局のところ IO モナドを完全に活用するのに、世界の状態も、圏論も、副作用の論議も何にもいらなかった。


IOモナドとの付き合い方(3)へ do 記法を使わない main 関数へ
[PR]
by tnomura9 | 2011-09-29 18:48 | Haskell | Comments(12)

IO モナドとの付き合い方(3)

IO モナドのプログラムは、IO モナド型の関数を >>= 演算子でつなげたものだ。しかし、>>= 演算子でつなぐとエラーが出る場合がある。次の例は putStrLn "hello" と putStrLn "world" を >>= 演算子でつないでいるが、ghci で実行するとエラーになる。

Prelude> putStrLn "hello" >>= putStrLn "world"

:1:21:
    Couldn't match expected type `() -> IO b'
        against inferred type `IO ()'
    In the second argument of `(>>=)', namely `putStrLn "world"'
    In the expression: putStrLn "hello" >>= putStrLn "world"
    In the definition of `it':
        it = putStrLn "hello" >>= putStrLn "world"

これは、putStrLn 関数の戻値が IO () なので、左辺から右辺への値の受け渡しが無いからだ。このような場合は >>= 演算子の代わりに、>> 演算子を使うとうまくいく。

Prelude> putStrLn "hello" >> putStrLn "world"
hello
world

したがって、IO モナドのルールその2は次のようになる。
ルール2: >>= 演算子の左辺からの戻値が無いときは >> 演算子を使う。
また、>> 演算子の定義は
x >> y = x >>= const y
であるから、>>= 演算子の左辺からの戻値を無視する場合にも >> 演算子を使うことができる。次の例では、getLine で入力された文字列は無視される。

Prelude> getLine >> putStrLn "hello"
world
hello

このように、>> 演算子は、演算子の左辺からの戻値がない場合、あるいは、左辺からの戻値を意識的に無視する場合に使われる、>>= 演算子のバリエーションだ。

注:

じつは >> は IO a >>= \_ -> f のように >>= をワイルドカード \_ で受けた場合のシンタックス・シュガーだ。したがって上の例は次のように書けば >>= 演算子を使って書くことができる。新しい >> 演算子が必要であるということではなく、あくまでも >>= が処理の本質だ。

Prelude> putStrLn "hello" >>= \_ -> putStrLn "world"
hello
world

IOモナドとの付き合い方(2)へ IO モナドとの付き合い方(4)へ
[PR]
by tnomura9 | 2011-09-29 12:43 | Haskell | Comments(0)

IOモナドとの付き合いかた(2)

IO モナドのプログラムをすっきりと理解するためには、IO モナドのプログラミングを本来の在り方でプログラムするのが一番だ。そのためには、まず入出力のプログラムを記述するのに do 記法を用いない

IOモナドを使った Haskell の入出力プログラムは、関数であって、関数プログラミングに閉じ込められれた手続き型のプログラムではない。副作用のある入出力を扱っていても、それは関数プログラミング以外の何者でもない。

do 記法はこの本来の関数型のプログラムを手続き型のプログラムに似せて記述するためのシンタックスシュガーでしかない。

したがって、入出力のプログラムを関数として捉えると、do 記法で発生した不可解なエラーを回避することができる。それでは、Haskell の入出力プログラムとはどのような関数なのだろうか。
ルール1: IOモナドのプログラムは、IOモナド型の値を >>= 演算子でIOモナド型の関数に与えるという操作の連鎖である。
IO モナドのプログラムのルールは、上のルール1で完全に規定できる。たとえば、次のようなプログラムは、IOモナドのプログラムだし、実際に稼働する。
return "hello, world" >>= putStrLn
return "hello, world" で返される値は IO モナド型の値で、 putStrLn は IO モナド型の関数だ。return "hello, world" で返される IO モナド型の値を >>= (bind) 演算子で putStrLn という IO モナド型の関数に与えられた結果の戻値は IO モナド型の値だ。

それでは、IOモナド型の関数とはどんな関数なのだろうか。それは、
入力の引数が一個で、出力の戻値が IO a 型の関数
である。IO モナド型の関数が、このような性質を持つ理由は >>= 演算子の性質にある。>>= 演算子は、左辺の関数の戻値の IO a 型から、a を取り出し、右辺の関数の引数として渡す働きがある。したがって、>>= 演算子の左辺の関数は IO a 型の戻値を返す必要があるし、右辺の関数はその値を受け取るために引数が一個だけでないといけない。

上の例で言えば return 関数は一個だけの引数 "hello, world" を取り、戻値として IO "hello, world" を返す。また、putStrLn は一個の文字列を引数に取り、IO () を戻値として返す。したがって、どちらもIO モナド型の関数なので >>= によって IO モナド型の戻値を連鎖させることができる。

そこで、 return "hello, world" >>= putStrLn がどのような動作をするかを見てみる。return 関数が返す戻値 IO "hello, world" から >>= 演算子は "hello, world" という引数を取り出し、左辺の putStrLn に引数として渡す。したがって、putStrLn "hello, world" が実行され、putStrLn 関数は IO () を返す。したがって >>= で連結された return "hello, world" >>= putStrLn の戻値も IO モナド型の値になる。

次の例は、ghci で上のプログラムを実行したものだ。
Prelude> return "hello, world" >>= putStrLn
hello, world

IO モナドのプログラミングのルールは、基本的にはこれだけだ。ルールのバリエーションも、>>= 演算子の代わりに >> を使う場合と、>>= 演算子の右辺が左辺の戻値を受け取る際に (\x -> func x) のようにラムダ記法で受け取る場合の2つしか無い。合計3つのルールだけで完全に IO モナドのプログラムを記述することができる。かなり、簡潔なルールだ。

次回は2つしかない >>= 演算子関連のバリエーションについて述べる。


IOモナドとの付き合い方(1)へ IO モナドとの付き合い方(3)へ

注: return や putStrLn は副作用を持つので厳密には関数の範疇には入れられない。しかし、これらの関数からの戻値 IO a のコンテナから a 型のデータを取り出す方法は >>= 演算子による他はないので、純粋関数は IO モナドのコンテナの値を利用できない。

副作用によって影響をうけるのはこのコンテナの値なので、純粋関数の側からは、たとえば、getLine の戻値 IO String のコンテナの中身がどのような文字であっても区別できないので、見かけ上 getLine はいつでも同じ値を返しているように見える。

副作用がIOモナドに閉じ込められていれば、IOモナド型の関数はあたかもふつうの関数であるかののように扱えるので、この記事では、あえてこれらのアクションを関数として取り扱う。
[PR]
by tnomura9 | 2011-09-29 08:08 | Haskell | Comments(0)

IO モナドとの付き合い方(1)

Haskell でとにかく評判が悪いのが IO モナドだ。理論的なことのわかりにくさももちろんだが、do 記法の中に普通の手続き型のプログラムを書けばいいと考えてプログラムを書くと、わけのわからないエラーが出て、その原因が全く分からないという事態が起きてしまう。

例えば、次のプログラムの do 記法の中身は手続き型のプログラムのものとよく似ている。

ファイル名: example1.hs
main = do
  cs <- getLine
  putStrLn cs

これは次のようにきちんと動く。
Prelude> :e example1.hs
Prelude> :l example1.hs
[1 of 1] Compiling Main ( example1.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
hello, world
hello, world

ところが、手続き型のプログラムとのアナロジーで次のようなプログラムを作ると、エラーが出てしまうが、何でエラーになるのか見当もつかない。

ファイル名: example2.hs
main = do
  name <- getLine
  greeting <- "hello, " ++ name
  putStrLn greeting

example2.hs の実行例は次のようになるが、コンパイルできずエラーになる。
*Main> :e example2.hs
*Main> :l example2.hs
[1 of 1] Compiling Main ( example2.hs, interpreted )

example2.hs:3:2:
    Couldn't match expected type `[Char]'
        against inferred type `IO Char'
    In a stmt of a 'do' expression: greeting <- "hello, " ++ name
    In the expression:
        do { name <- getLine;
            greeting <- "hello, " ++ name;
            putStrLn greeting }
    In the definition of `main':
        main = do { name <- getLine;
            greeting <- "hello, " ++ name;
        putStrLn greeting }
Failed, modules loaded: none.

次のようなプログラムにするのが正解で、これはコンパイルできて実行できる。

ファイル名: example3.hs
main = do
  name <- getLine
  greeting <- return ("hello, " ++ name)
  putStrLn greeting

実行例
Prelude> :e example3.hs
Prelude> :l example3.hs
[1 of 1] Compiling Main ( example3.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
world
hello, world

要は "hello, " ++ name の値を return 関数の引数にするだけだが、この例では何で、return 関数の引数にすると良いのか合点がいかない。

上の例で見てきたように、IO モナドをプログラムを手続き型のプログラムからの類推で組もうとすると、どうしても意味不明のエラーに突き当たって、IO モナドは難しい、Haskell は使えないという結論になりがちだ。

しかし、これは、IO モナドのプログラムを、手続き型のプログラムの類推で組もうとするから起こる困難で、一旦手続き型のプログラムのことは忘れて、本来のIO モナドのプログラム規則に従ってプログラムを作成してみると、IO モナドのプログラムの規則が非常に単純で分かりやすいことが実感できる。

実はこのプログの System.IO モジュールのテストに用いたプログラムが、この「本来のIOモナドのプログラム様式に素直に従ったプログラム」だ。記事の通りに ghci のコマンドライン上で入力して実行してみれば、IO モナドのプログラミングの規則が非常に単純で、すっきりしているのが分かるだろう。あんなに難解だったIOモナドのプログラムを、ほとんどエラーもなく組めるようになっているのに気づくと思う。

次からの記事は、IOモナドプログラムのこの単純なルールに焦点をあて、IOモナドのプログラミングのルールが、実は、非常に単純ですっきりしたものだということを示そうと思う。


IO モナドとの付き合い方(2)へ
[PR]
by tnomura9 | 2011-09-28 18:39 | Haskell | Comments(0)

System.IO モジュール(18) テンポラリ・ファイル

System.IO モジュールの Temporary files セクションの関数は、テンポラリ・ファイルを操作するための関数だ。openTempFile はテンポラリ・ファイルをオープンする関数で openFile 関数と同様な使い方をする。openBinaryTempFile はバイナリのテンポラリ・ファイルをオープンする関数だ。

openTempFile の型宣言は次のようになっている。

openTempFile
  :: FilePath  Directory in which to create the file
  -> String  File name template. If the template is "foo.ext" then the created file will be "fooXXX.ext" where XXX is some random number.
  -> IO (FilePath, Handle)

openTempFile の第1引数は一時ファイルが入るディレクトリのパスだ。第2引数はテンポラリファイルの先頭のファイル名、その後に適当な番号をつけて関数が新しいファイルを作り出す。テンポラリファイルは、読み出し・書き込みモードでオープンされる。

作成されたテンポラリ・ファイルは自動で削除されるわけではないので、ファイルの削除は手動で行う必要がある。

次の例は、Windows 上の GHCI で openTempFile をテストした例だ。

Prelude System.IO> openTempFile "" "temp" >>= (\(path, hdl) -> hPutStrLn hdl "hello" >> putStrLn path)
temp3708
Prelude System.IO> :! type temp3708
hello

Temporary files セクションには openTempFile 以外に次のような3つの関数があるが、説明は省略する。

openBinaryTempFile :: FilePath -> String -> IO (FilePath, Handle)
openTempFileWithDefaultPermissions :: FilePath -> String -> IO (FilePath, Handle)
openBinaryTempFileWithDefaultPermissions :: FilePath -> String -> IO (FilePath, Handle)
[PR]
by tnomura9 | 2011-09-27 13:11 | Haskell | Comments(0)

System.IO モジュール(17) Binary input and output

System.IO モジュールの Binary input and output セクションの関数は、バイナリーファイルの読み書きをする関数だ。バイナリーファイルの読み書きなどやったことがないので、このセクションはとばそうかと思ったが、読み込みの実験だけはやってみた。

withBinaryFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r 関数の使い方は、withFile 関数と同じだが、バイナリーモードでファイルをオープンする。

Prelude System.IO> withBinaryFile "hello.txt" ReadMode (\hdl -> hGetContents hdl >>= print)
"hello, world\r\nhow are you?\r\n"

次の withFile の実行例と比べると、改行文字の自動変換はせずに、忠実にコードを読み出していたのが分かる。

Prelude System.IO> withFile "hello.txt" ReadMode (\hdl -> hGetContents hdl >>= print)
"hello, world\nhow are you?\n"

これだけでは本当にバイナリーファイルを読み出しているのか不安なので、本物のバイナリーファイルを試してみたがどうやらちゃんと読み出しているようだ。

Prelude System.IO> withBinaryFile "hello.o" ReadMode (\hdl -> hGetContents hdl >>= print)
"L\SOH\ENQ\NUL\NUL\NUL\NUL\NUL\DLE\EOT\NUL\NUL%\NUL\NUL\NUL\NUL\NUL\EOT\SOH.text (以下略)

openBinaryFile :: FilePath -> IOMode -> IO Handle 関数の使い方も、openFile 関数と同じだ。

Prelude System.IO> openBinaryFile "hello.txt" ReadMode >>= (\hdl -> hGetContents hdl >>= print >> hClose hdl)
"hello, world\r\nhow are you?\r\n"

hSetBinaryMode :: Handle -> Bool -> IO () 関数はファイルハンドルのモードをバイナリーモード(True)にしたり、テキストモード(False)にしたり切り替えることができる。

Prelude System.IO> withBinaryFile "hello.txt" ReadMode (\hdl -> hSetBinaryMode hdl False >> hGetContents hdl >>= print)
"hello, world\nhow are you?\n"

上の例では、ファイルをバイナリーモードでオープンしたものの、hSetBinaryMode でファイルハンドルをテキストモードにしているので、ファイルの読み出しがテキストモードになっている。

Binary input and output セクションには他に次のような関数があるが、バイナリーファイルの書き込みの基本的なことを知らないので、残念ながら、スキップする。

hPutBuf :: Handle -> Ptr a -> Int -> IO ()
hGetBuf :: Handle -> Ptr a -> Int -> IO Int
hGetBufSome :: Handle -> Ptr a -> Int -> IO Int
hPutBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int
hGetBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int
[PR]
by tnomura9 | 2011-09-22 18:32 | Haskell | Comments(0)

System.IO モジュール(16) interact の応用

System.IO モジュールinteract :: (String -> String) -> IO () 関数を前回調べたが、これを使うとプログラムに速攻で間に合わせのインタラクティブな動作を付与することができる。その理由は2つある。

ひとつは、interact を使えば、 String -> String 型の関数であればなんでも標準入出力をおこなうプログラムにすることができるということだ。プログラマは IO モナドを一切プログラムしなくて済む

String -> String 型の関数というのは、文字列を入力して文字列を出力する関数だから、インタラクティブな操作をするプログラムのほとんど全部がそういうプログラムだ。

もう一つの理由は、interact が内部で getContents 関数を利用しているため、動作が遅延評価に対応しているということだ。それは、どういうことかというと、手続き型のプログラムでループで表現しているような繰り返しの操作を、無限リストとして操作できてしまう

Haskell の特徴として、プログラムについて話すよりもプログラムに話させる方が分かりやすいというのがあるので、プログラム例を上げてみる。

ファイル名: example1.hs
module Main where

main = do interact hello

hello cs = unlines $ map ("hello, " ++) $ lines cs

上のプログラムの関数 hello は改行区切りの文字列を引数にとり、区切られた文字列の先頭に "hello, " をつけた文字列を改行で区切って戻り値として戻す関数だ。言葉で説明するより、次の実行例を見てもらったほうが早いかもしれない。
*Main> hello "world\nthere"
"hello, world\nhello, there\n" 

この hello 関数の型は String -> String 型だから、interact の引数にして標準入出力上のインタラクティブなプログラムに変更することができる。次の実行例はこの example1.hs をコンパイルした example1.exe の動作を見たものだ。
C:\Users\********\Documents\Haskell>ghc example1.hs -o example1.exe

C:\Users\********\Documents\Haskell>example1
world
hello, world
there
hello, there
^Z

ループも何も記述していないのに、キーボードからの入力に対し繰り返し同じ作業をするプログラムが出来上がってしまった。これが、interact を利用する第2の理由の実例で、改行区切りの文字列を入力して改行区切りの文字列を出力する関数を interact の引数にすると上の例のような繰り返しの動作をする入出力プログラムを作り出すことができる。

hello の入力が標準入力の場合、端末からEOFが入力されない限り永遠にプログラムとの対話が続けられる。これが、interact が遅延評価に対応している利点の実例だ。

プログラムの入力を中断するのにいちいち ^Z を入力しなければならないのは抵抗があるので、プロンプトから空行を入力したときはプログラムを終了できるように、example1.hs を修正して example2.hs を作ってみた。

プログラム名: example2.hs
module Main where

main = do interact hello

hello cs = unlines $ takeWhile (/= "hello, ") $ map ("hello, " ++) $ lines cs

このプログラムでは、hello 関数が takeWhile 関数で条件に合うものだけを拾い上げるようにしたため、条件に合わない入力のところでプログラムが終了する。実行例は次のようになり、ずいぶん普通のプログラム風の動作になっている。
C:\Users\********\Documents\Haskell>ghc example2.hs -o example2.exe

C:\Users\********\Documents\Haskell>example2
world
hello, world
there
hello, there


C:\Users\********\Documents\Haskell>

このように、改行区切りの文字列を入力して、改行区切りの文字列を出力する関数を書けば、interact 関数を使って簡単インタラクティブなプログラムを Haskell で書くことができる。

追記

interact が引数に String -> String 型の関数をとるということは、それが単にフィルタープログラムの作成支援をしているだけなのかもしれない。そう考えれば String -> String 型のプログラムを ghci 上で開発して完成したら、その後は interact 関数に IO 関係のことは任せられるのでこんな便利なことはない。
[PR]
by tnomura9 | 2011-09-21 12:56 | Haskell | Comments(0)

System.IO モジュール(16) 標準入力、標準出力の操作

System.IO モジュールの Special cases for standard input and output セクションの関数は標準入出力を操作する関数だ。これらは、全て Prelude から利用できる関数でもある。その大半は、getLine や、putStr など頻繁に使用してなじみの深い関数ばかりだが、ただひとつ interact 関数だけが使用法がよくわからない。

interact 関数は次の型宣言のように、「文字列の引数をとり、文字列の戻り値を戻す関数」を引数としている。

interact :: (String -> String) -> IO ()

これだけでは分からないので、interact のソースを見てみると次のようになっている。

interact :: (String -> String) -> IO ()
interact f = do s <- getContents
                  putStr (f s)

これは、interact 関数が、標準入力から文字列を getContents 関数で読み取り、その文字列を関数 f で加工した戻り値の文字列を標準入力に putStr 関数で出力しているという意味だ。

たとえば、次の関数 hello のように、文字列 cs が与えられるとその先頭部分に "hello, " を付け加えて戻り地として戻す関数を作ったする。

hello cs = "hello, " ++ cs

この関数に interact を適用して次のように main 関数を定義しておくとこのプログラムは標準入力から入力した文字列に "hello, " を付け加えて標準出力に出力する。

main = do interact hello

標準入出力を操作するプログラムは ghci ではテストできない場合があるので、プログラムファイルを作ってコンパイルしてテストしてみる。まず、次のようなソースファイル intrct を作成する。

ファイル名: intrct.hs
module Main where

main = do interact hello

hello cs = "hello, " ++ cs

これを次のようにして ghc でコンパイルする。

>ghc intrct.hs -o intrct.exe

実行例は次のようになる。EOFが来ない限りプログラムは終了しないので、^Z で終了させる。

>intrct.exe
world
hello, world
^Z

ところで、上の interact の定義を見ると、標準入力からの文字列は getContents 関数で取り込まれている。getContents 関数は遅延評価に対応している。ということは、getContents 関数を利用した intaract 関数は、標準入力に文字列が入力されるたびに処理が行われるようにできるということだ。

そこで、上の hello 関数を改造して、改行で区切られた文字列を lines 関数でリストに変換し、リストのそれぞれの要素の先頭に "hello, " を付加した要素のリストを作り、そのリストを再び改行で区切られた文字列に変換するようにしてみた。

hello cs = unlines $ map ("hello, " ++) $ lines cs

この新しい hello を ghci で試してみた。

*Main> hello "world\nDolly"
"hello, world\nhello, Dolly\n"

この新しいプログラムに intrct2.hs と名前をつけてコンパイルした。

ファイル名: intrct2.hs

module Main where

main = do interact hello

hello cs = unlines $ map ("hello, " ++) $ lines cs

これをコンパイルして実行したものが次の実行例だ。

>intrct2.exe
world
hello, world
Dolly
hello, Dolly
^Z

新しい名前を入力するたびに、"hello, " をつけて表示してくれる。getLine や putStr などを使ったり、繰り返しのための再帰的定義によるループを記述することなく、同じ作業を繰り返してくれるインタラクティブなプログラムを記述できている。標準入出力へのインターフェースのプログラムが、一様に

main = do interact <function>

という形で一行で書けてしまうのは、ありがたい。ちょっと感動してしまった。IO モナドのループは記述が難しいが、このように遅延評価を利用してリスト処理で繰り返しの操作を記述してしまうというのは面白いテクニックだ。

このセクションの他の関数は良く使ってなじんでいるはずなので、型宣言のみ次に表示しておく。

putChar :: Char -> IO ()
putStr :: String -> IO ()
putStrLn :: String -> IO ()
print :: Show a => a -> IO ()

getChar :: IO Char
getLine :: IO String
getContents :: IO String
readIO :: Read a => String -> IO a
readLn :: Read a => IO a

上の関数で readIO と readLn 関数は、文字列を指定した型のデータに変換する Prelude の関数 read のIOモナド版だ。readLn = getLine >>= readIO。文字列をどのような型に変換するかは、関数の型宣言で指定する。

プログラム名: test_read.hs

rList :: IO [List]
rList = readLn

実行例

Prelude> :e test_read.hs
Prelude> :l test_read.hs
[1 of 1] Compiling Main
Ok, modules loaded: Main.
*Main> rList
[1,2,3]
[1,2,3]
[PR]
by tnomura9 | 2011-09-21 03:58 | Haskell | Comments(0)

System.IO モジュール(15) Text output

System.IO モジュールの Text output セクションの関数は文字列をファイルハンドルに出力する機能があるが、入力の場合と違い動作は単純で迷うことはない。

hPutChar :: Handle -> Char -> IO () 関数は、一文字をファイルハンドルに出力する。

Prelude> :m System.IO
Prelude System.IO> hPutChar stdout 'a'
aPrelude System.IO>

hPutStr :: Handle -> String -> IO () 関数は、文字列をファイルハンドルに出力する。

Prelude System.IO> hPutStr stdout "hello"
helloPrelude System.IO>

hPutStrLn :: Handle -> String -> IO () 関数は、文字列に改行文字を付け加えてファイルハンドルに出力する。

Prelude System.IO> hPutStrLn stdout "hello"
hello
Prelude System.IO>

hPrint :: Show a => Handle -> a -> IO () はいろいろなデータ型のデータを文字列に変換して出力する。

Prelude System.IO> hPrint stdout stdin
{handle: <stdin>}
[PR]
by tnomura9 | 2011-09-20 15:40 | Haskell | Comments(0)

System.IO モジュール(14) Text input

System.IO モジュールの Text input セクションの関数は、文字列入力を取り扱う。(次の実験は Windows のコマンドプロンプトがエコーバックしながら、入力の時はラインバッファリングで動作するという特殊な環境を利用したものだ。Linux ではうまく動かない。)

hWaitForInput :: Handle -> Int -> IO Bool 関数は Handle のファイルハンドルから文字の入力を Int ミリ秒だけ待つ。その時入力があれば True を、入力がなければ False を返す。

Prelude> :m System.IO
Prelude System.IO> hWaitForInput stdin 10000
hello
True

hReady :: Handle -> IO Bool 関数は、ファイルハンドルから読み出せる文字があるときは True を、そうでないときは False を返す。次の例は、hWaitForInput 関数で標準入力のバッファに文字列を入力した後、hReady を実行すると True が返る。その後、getLine で標準入力のバッファを空にして、再度 hReady を実行すると False が返る。

Prelude System.IO> hWaitForInput stdin 10000
hello
True
Prelude System.IO> getLine
"hello"
Prelude System.IO> hReady stdin
False

hGetChar :: Handle -> IO Char 関数はファイルハンドルから一文字読み出す。

Prelude System.IO> hGetChar stdin
a
'a'

hGetLine :: Handle -> IO String 関数はファイルハンドルから一行読み出す。

Prelude System.IO> hGetLine stdin
hello, world
"hello, world"

hLookAhead :: Handle -> IO Char はファイルハンドルから一文字読み出すが、バッファからファイルを取り去らずに残しておく。

Prelude System.IO> hWaitForInput stdin 10000
hello, world
True
Prelude System.IO> hLookAhead stdin
'h'
Prelude System.IO> getLine
"hello, world"

これとは違って、hGetChar で読み出すとバッファの先頭の一文字は失われる。

Prelude System.IO> hWaitForInput stdin 10000
hello, world
True
Prelude System.IO> hGetChar stdin
'h'
Prelude System.IO> getLine
"ello, world"

hGetContents :: Handle -> IO String 関数は、ファイルハンドルの内容を全て読みだすが、遅延評価ができる。

Prelude System.IO> withFile "hello.txt" ReadMode (\hdl -> hGetContents hdl >>= putStr)
hello, world
how are you?

追記

Ubuntu での実験結果。上の例とは違うが、Linux のターミナルでは hWaitForInput を使ったトリックで stdin のバッファに値を残せるのは1文字だけだから。

Prelude System.IO> hWaitForInput stdin 10000
aTrue
Prelude System.IO> hReady stdin
True
Prelude System.IO> hLookAhead stdin
'a'
Prelude System.IO> hReady stdin
True
Prelude System.IO> hGetChar stdin
'a'
Prelude System.IO> hReady stdin
False
[PR]
by tnomura9 | 2011-09-20 13:00 | Haskell | Comments(0)