Haskell の入り口の小さなこと
使うつもりのなかった Haskell を今年になってから使おうと思い始めた。
そして Haskell の手始めに詰将棋の解図プログラムを書いてみようと思いついた。
しかし、その前にいつもやっている、メッセージを出して、入力を受け付け、計算を行い、出力す る、という、ぼくにとっての Hello World ともいえる処理を書いてみることにした。
日本国内にも Haskell を使っている人はたくさんいるはずと思うのだが、今回ぼくが書く、Haskell の入り口の小さなことを知っているのだろうか?
まずは、在り来たりだけど、こんなプログラムを書いてみた。
------------------------------------------------------------------------
import Data.Char
fib :: Int -> Integer
fib = (!!) $ fib 0 1 where fib a b = a : fib b (a + b)
fibds :: Int -> Int
fibds = length . map digitToInt . show . fib
main = do
putStr "computing fib(N), input N (only enter to quit) : "
n <- getLine
if null n
then return ()
else do
putStr $ "\nfib(" ++ n ++ ") = "
putStrLn $ show $ fib $ read n
putStr $ "digits length of fib(" ++ n ++ ") = "
putStrLn $ (show $ fibds $ read n) ++ "\n"
main
------------------------------------------------------------------------
そして、GHCi で動かしてみると、以下のように思った通りの結果だ。
------------------------------------------------------------------------
ghci> :main
computing fib(N), input N (only enter to quit) : 100
fib(100) = 354224848179261915075
digits length of fib(100) = 21
------------------------------------------------------------------------
うん、楽勝、楽勝と今度はコンパイルして動かしてみる。
------------------------------------------------------------------------
C:\hs>ghc fibds0.hs
[1 of 1] Compiling Main ( fibds0.hs, fibds0.o )
Linking fibds0.exe ...
C:\hs>fibds0
100
computing fib(N), input N (only enter to quit) :
fib(100) = 354224848179261915075
digits length of fib(100) = 21
------------------------------------------------------------------------
あれっ、最初の putStr "computing fib(N), input N (only enter to quit) : " が表示されないで、入力待ちになっているぞ! 仕方がないので、100 を入力。
そう、GHCi 上とコンパイル後のプログラムでは挙動が異なるのだ。
ぼくはまずコンパイラのスイッチを調べたが、そこじゃない。その後もあれこれ悩んだあげくやっとわかった!
GHCi のデフォルトは、行バッファリングがオフなのだ。それに対し、コンパイラが吐きだした実行プログラムでは、行バッファリングがオンになっている。
putStrLn "message"
s <- getLine
↑のように putStrLn にすれば問題はないが、ぼくはそれでは不満なのだ。Python でできることが Haskell にはできないというのか。
System.IO のなかに、hGetBuffering stdout(現在のバッファリングを知る)、hSetBuffering stdout NoBuffering(行バッファリングをオフにする)、hSetBuffering stdout LineBuffering(行バッファリングをオンにする)などを行うことができるモジュールが入っている。
------------------------------------------------------------------------
import System.IO
main = print =<< hGetBuffering stdout
------------------------------------------------------------------------
↑これを GHCi で実行してみると、以下のとおり、行バッファリングがオフであることがわかる。
------------------------------------------------------------------------
ghci> :load "letmeknow_linebuffering.hs"
[1 of 1] Compiling Main ( letmeknow_linebuffering.hs, interpreted )
Ok, modules loaded: Main.
ghci> :main
NoBuffering
------------------------------------------------------------------------
今度は同じプログラムをコンパイルして実行してみよう。以下のとおり、行バッファリングがオンになっていることが確認できる。
------------------------------------------------------------------------
C:\hs>ghc letmeknow_linebuffering.hs
[1 of 1] Compiling Main ( letmeknow_linebuffering.hs, letmeknow_linebuffering.o )
Linking letmeknow_linebuffering.exe ...
C:\hs>letmeknow_linebuffering
LineBuffering
------------------------------------------------------------------------
では、hSetBuffering stdout NoBuffering を使って行バッファリングをオフにすればよいのか。多分部分的にはうまくいきそうだが、これはやるべきではないように思う。
そこで、行バッファリングをフラッシュする hFlush stdout を使うことにした。これだと1行だけの問題として解決できるはずだ。
------------------------------------------------------------------------
import System.IO
import Data.Char
fib :: Int -> Integer
fib = (!!) $ fib 0 1 where fib a b = a : fib b (a + b)
fibds :: Int -> Int
fibds = length . map digitToInt . show . fib
main = do
putStr "computing fib(N), input N (only enter to quit) : "
hFlush stdout -- ここに注目!(import System.IO が必要)
n <- getLine
if null n
then return ()
else do
putStr $ "\nfib(" ++ n ++ ") = "
putStrLn $ show $ fib $ read n
putStr $ "digits length of fib(" ++ n ++ ") = "
putStrLn $ (show $ fibds $ read n) ++ "\n"
main
------------------------------------------------------------------------
やれやれ、やっと解決だね。
このような問題に関する記事を書籍やネットで見たことがないが、他のみんなは解決策を既に知っているということなのだろうか。
小さいように思えてぼくにとっては大きな問題だった。
いずれにせよ Haskell の入り口の小さなことでした。