魔術師をめざして

魔術師を目指して、相場・数学・プログラム言語を研究しています。

Haskell の入り口の小さなこと

使うつもりのなかった Haskell を今年になってから使おうと思い始めた。

 f:id:fxrobot:20150214150232j:plainそして 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 の入り口の小さなことでした。