「ふつうのHaskellプログラミング」読みます(1)

Haskel Hackathlonに申し込んだ手前Haskell知らなくちゃ話になんねえので、通称ふつハス本買ってきました。とりいそぎ2章あたりまで読んだので手元で試した記録のっける。
私の知識はHaskell知識はhttp://www.kmonos.net/wlog/65.html#_1549060908読んだことあるくらいです。

はろわはろわ

% ledit >hello.hs
main = putStrLn "Hello, World!"
% runghc hello.hs
Hello, World!

モナドとか怖いとかはろわですらむずかしいって言ったの誰だ! (少なくとも一見)簡単じゃないか!
leditは便利なcatコマンドみたいなものです。

じゃあ以下2章で例示されてるプログラムを順にやってく。

cat

% ledit >cat.hs
main = do cs <- getContents
          putStr cs
% runghc cat.hs <cat.hs
main = do cs <- getContents
          putStr cs
% ghc cat.hs -o cat
% ./cat
abc
abc
def
def

なんかcatできてますね! なんですかC言語とかJavaなんかよりずっと楽じゃないですか。これが一括読み込み書き込みじゃなくて逐次処理になる理由はよくわかんないけど。
getContentというアクションをcsに束縛して、putStrにcsアクションを渡すとかなんとか。getContentのアクションは評価が遅延されて、putStrに渡されたときに入力とってくるアクションが評価されてそれをputStrするアクションが評価される、とか想像してみた。よくわかんないですね。
ところで、このcat.hsをrunghcするとわけわからんこってなりました。

% runghc cat.hs
aabbcc

^D

cat.hs: exception :: GhcException

なんでしょうね。^D打っても終了できなかったんで^Cで終了しました。よくわかんないですね。先に進みましょう!

countline

つぎはcountlineコマンド。wc -l相当ですね。

% ledit >countline.hs
main = do cs <- getContents
          print $ length $ lines cs
% ghc countline.hs -o countline
% ./countline
aaaaaaa
bbbbbb
ccc
d
4

linesで切り分けてlengthで数えてprintで書く。むずかしいことは無いですね。getContentとかの真意とかを考えないでいれば。あと個人的に

% ledit >countline.hs
main = do cs <- getContents
          (print (length (lines cs)))

こう書くとちょっと安心するかも! linesとlengthは名前通りのこんなんだよねー。

Prelude> lines "1\n22\n333\n444"
["1","22","333","444"]
Prelude> length $ lines "1\n22\n333\n444"
4

head

tailコマンド知ってたけどheadコマンド知らんかった。

% cat head.hs
main = do cs <- getContents
          putStr $ firstNLines 10 cs

firstNLines n cs = unlines $ take n $ lines cs
% ghc head.hs -o head
% ./head </usr/include/stdio.h
/* Define ISO C stdio on top of C++ iostreams.
...

できたできた。これはこういうことですね

Prelude> lines "1\n22\n333\n4444"
["1","22","333","4444"]
Prelude> take 2 $ lines "1\n22\n333\n4444"
["1","22"]
Prelude> unlines $ take 2 $ lines "1\n22\n333\n4444"
"1\n22\n"

tail

headの逆。

% cat tail.hs
main = do cs <- getContents
          putStr lastNLines 10 cs

lastNLines n cs = unlines $ takeLast n $ lines cs

takeLast n ss = reverse $ take n $ reverse ss
% ghc tail.hs -o tail

tail.hs:2:17:
    Couldn't match expected type `String'
           against inferred type `Int -> String -> String'
    In the first argument of `putStr', namely `lastNLines'
    In the expression: putStr lastNLines 10 cs
    In the expression:
        do cs <- getContents
           putStr lastNLines 10 cs

おおっと噂のおそろしいエラーが出てきましたね。tail.hsの2行目の17文字目が怪しいらしいってさ。
「期待してる型はStringなのにInt -> String -> Stringとかいう型の値が来てるよー」とかかな。putStrにlastNLinesを渡してるからこんなこと言われたんですね。putStrの後ろに$を付けるの忘れてました。

% cat tail.hs
main = do cs <- getContents
          putStr $ lastNLines 10 cs

lastNLines n cs = unlines $ takeLast n $ lines cs

takeLast n ss = reverse $ take n $ reverse ss
% ghc tail.hs -o tail
% ./tail </usr/include/stdio.h
...
#endif /* !_STDIO_H */

できましたね。よかったよかった。新たに使ってんのはreverseくらいですかね。

Prelude> reverse [1,2,3,4,5]
[5,4,3,2,1]
Prelude> take 2 $ reverse [1,2,3,4,5]
[5,4]

ほうほう。

まとめ

putStr, putStrLn, printが返すのは「アクション」らしいですよ。getContentsとやらは「アクション」そのものらしい。なんでしょうね、アクション。夢が広がりますね。他の関数に関してはごく普通に色々してくれるリスト操作の関数でしたね。

練習問題

countbyte

こんなんかなー

% cat countbyte.hs
main = do cs <- getContents
          print length cs
% ghc countbyte.hs -o countbyte

countbyte.hs:2:10:
    Couldn't match expected type `String -> IO t'
           against inferred type `IO ()'
    In the expression: print length cs
    In the expression:
        do cs <- getContents
           print length cs
    In the definition of `main':
        main = do cs <- getContents
                  print length cs

またやっちまった。これだとprintにlengthとcsを渡してることになっちゃう。こうですね。

% cat countbyte.hs
main = do cs <- getContents
          print $ length cs
% ghc countbyte.hs -o countbyte
% ./countbyte <countbyte
399922
% ls -l countbyte
-rwxr-xr-x 1 sun sun 399922 2008-01-27 04:00 countbyte*

あってるあってる。

countwords
% cat countword.hs
main = do cs <- getContents
          print $ length $ words cs
% ghc countword.hs -o countword
% ./countword <countword.hs
12

えーと、あってますね。よかったよかった。

あそび

文字列は文字のリストらしいですよ。あと中置記法演算子は、例えば+なら(+)がそのものらしいですよ。構文糖ですねさては。

Prelude> []
[]
Prelude> 2 : 1 : []
[2,1]
Prelude> ((:) 2 ((:) 1 []))
[2,1]
Prelude> 1 + 2 + 3
6
Prelude> ((+) 1 ((+) 2 3))
6
Prelude> take 2 $ reverse [1,2,3,4,5]
[5,4]
Prelude> (($) take 2 (reverse [1,2,3,4,5]))
[5,4]

これなんてLisp。$ってただ評価するだけなのな、たぶん。

あー、ねむいよねむいよ。おやすみ。

test