あどけない話

インターネットに関する技術的な話など

はじめての Haskell

昨日、友達にこんなことを話しました。

  • Haskell でプログラミングするときは、とりあえず効率のことは忘れる。
  • モリーは無限にあると考え、コンパイラーと遅延評価が頑張ってくれると信じる。
  • Haskell では what を記述する。
  • 効率を考えている時点で、how である。
  • what と how は、同一視されがちであり、区別するには訓練が必要。
  • 変数は初期化できるが、再代入できない。
  • だから、インデックスが必要な for はない。
  • 繰り返しが本質なら、再帰で書く。
  • 単にリストを走査したいなら map を使う。
  • リスト処理が得意なので、なんでもリストに落とし込む。
  • もう一度言うけれど、メモリーは無限にあると考えるから、リストが大きくても気にしない。

行を数えてみる

ファイルの行数を数えるプログラムを考えるとします。命令型の頭で考えると、一行ずつ読み込みながらファイルの終わりまでループを回し、行数の変数を増加させて行くでしょう。でも、Haskell には再代入はないのです。どうしますか?

一行ずつ読み込むと思う時点で、効率のことを考えています。つまり、how なんです。

Haskell では、メモリーが無限にあると考えるのでファイル全体を読み込みましょう。リスト処理が得意なので、改行で分割して「行のリスト」にしましょう。あとは、行数を数える、すなわちリストの大きさを取ればいいのです。

こんな感じになります。

main = do{ cs <- getContents
         ; print $ length $ lines cs
	 }

lines が、文字列から文字列のリストを作る関数です。

九九

九九を実装しようとすると、for 文の中で for 文を回したくなります。どう再帰したらいいのかわ分らないと言われました。

3 x 3 で考えてみましょう。

Haskell では、以下のようにリストを省略して書けます。

[1..3] → [1, 2, 3]

たとえば、リストのそれぞれの要素に 2 を掛けたいなら、map が使えます。\ で始まるのは、無名関数です。

map (\x -> 2 * x) [1..3] → [2,4,6]

横方向の計算はこれで OK なので、縦方向に再帰すればいいでしょう。

kuku [] lst = []
kuku (n:ns) lst = map (\x -> n * x) lst : kuku ns lst

kuku [1..3] [1..3] → [[1,2,3],[2,4,6],[3,6,9]]

あっという間にできました!

表示の方が難しいように思います。それぞれの要素を出力しようとすると、IO モナドのお化けになって訳が分からなくなるので、大きな文字列に変換し、最後に出力することにします。

まず、内側のリストを文字列に変換する関数を定義します。桁数を指示して数値を文字列に変換する標準的な方法が分らなかったので、少々みっともない実装になっています。

rowToLine = concatMap (\n -> (if n < 10 then " " else "") ++ (show n) ++ " ")

rowToLine [1, 2, 3] → " 1  2  3 "

rowToLine を数値のリストのリストに map すると、文字列のリストになります。文字列のリストを文字列に直すには、line の反対で unlines を使います。これを putStr すれば OK です。

display ll = putStr $ unlines $ map rowToLine ll

最後にプログラム全体を載せておきます。

row = 9
col = 9
kuku [] lst = []
kuku (n:ns) lst = map (\x -> n * x) lst : kuku ns lst
table = kuku [1..row] [1..col]
rowToLine = concatMap (\n -> (if n < 10 then " " else "") ++ (show n) ++ " ")
display ll = putStr $ unlines $ map rowToLine ll
main = display table

main →
 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 

本題

数値を希望の桁の文字列に直す標準的な関数があれば、教えて下さい。(format とか sprintf のようなイメージ。) 「ふつうの Haskell プログラミング」では、format を自前で実装していました。。。