<   2010年 11月 ( 28 )   > この月の画像一覧

汎用入力関数

リストをテーブル表示するプログラムが Haskell のデータ出力に汎用できると書いたが、入力の汎用関数があると便利だと考えた。そこで、次のようなプログラムを作ってみた。

genin = do { cs <- getLine; if cs == "" then return [] else do { rest <- genin; return (words cs ++ rest) } }

関数 genin をコマンドラインから実行すると、文字列を入力できる。文字列をスペースで区切って入力すると配列の要素として入力することができる。改行をしても入力は続くが、空行を入力すると終了して、IO型の配列で要素が文字列になったものを戻値として戻してくる。

Main> genin >>= print
aaa bbb ccc
ddd eee fff

["aaa","bbb","ccc","ddd","eee","fff"]

次の例はスペースで区切った数値を入力してその総和を計算している。

Main> genin >>= return . map (read::String->Int) >>= return . sum >>= print
1 2 3
4 5 6

21

do 記法を使ったほうが若干分かりやすくなる。

Main> do {xs <- genin; print (sum $ map (read::String->Int) xs) }
1 3 2
4 5 6
7 8 9
10

55

コンソールからの入出力はIOモナドの世界で行わなければならないので、扱いがやや面倒になる。処理の大半の部分を純粋関数の世界で作っておいて、必要最小限の部分をIOモナドの世界で行わせるようにした方がいい。

前回の記事と合わせて、汎用の入出力関数が手に入ったので、アルゴリズムの部分にだけ注力できるようになった。すばらしい Haskell 世界の入り口に立ったような気分だ。


今日のHaskell
List> transpose [[1,2,3],[4,5,6]]
[[1,4],[2,5],[3,6]]

(Data.)List モジュールの transpose は転置行列を作る。
[PR]
by tnomura9 | 2010-11-30 17:48 | Haskell | Comments(0)

リストをテーブル表示する

Haskell のデータ処理はほとんどがリストの上で行われる。したがって、Haskell のリストをテーブル表示する方法が分かっていると便利だと考えた。そこで、やってみたのが次の mktbl と showtbl の二つの関数だ。

import Data.List

mktbl n xs = if xs == [] then [] else take n xs : mktbl n (drop n xs)

showtbl xs = unlines (map (concat . intersperse "\t") xs)

全部1行なのでコマンドライン上で定義しても使える。

mktbl n xs はリスト xs を n 列のカラムからなるテーブルに変換する。
Main> mktbl 3 [1..9]
[[1,2,3],[4,5,6],[7,8,9]]

showtbl はテーブルのデータをタブで区切って行ごとに改行を入れた文字列を作る。表示には putStr を併用する。
Main> putStr $ showtbl [["hello","world"],["hi","there"]]
hello world
hi    there

数値のリストは map show で文字列表示に変更してから使う。
Main> putStr $ showtbl $ mktbl 4 (map show [1..20])
1    2    3    4
5    6    7    8
9    10   11   12
13   14   15   16
17   18   19   20

リストのテーブル表示が出来れば大抵の情報処理の出力に利用出来る。

shwotbl で表示をせずに文字列を作成するだけというのが奇異な感じがするかもしれないが、Haskell の場合はぎりぎりまで純粋関数の世界でデータをこしらえて、IOモナドの出番をできるだけ少なくするのがコツのようだ。また、そのほうがモジュール化されて応用がききやすい。頭の中でも純粋関数の世界とIOモナドの世界を区別しておいたほうが良い。
[PR]
by tnomura9 | 2010-11-30 10:11 | Haskell | Comments(0)

Haskell で虫食い算を解く

Haskell で虫食い算を解いた。とは言っても全自動ではなく、Haskell の助けを借りながら解いただけだ。

まず、Haskell で問題を表示する関数を記述した。

dispExp xs = putStrLn $ unlines $ map (unwords . map showNum) xs
  where
    showNum x = case x of
                  -3 -> "-"
                  -2 -> " "
                  -1 -> "*"
                  _ -> show x

これを使って問題を表示した。
Main> dispExp [[-2,-2,-1,-1,7],[-2,-2,3,-1,-1],[-3,-3,-3,-3,-3],[-2,-1,0,-1,-1],[-2,-1,-1,-1,-2],[-1,5,-1,-2,-2],[-3,-3,-3,-3,-3],[-1,7,-1,-1,3]]
     * * 7
     3 * *
- - - - -
   * 0 * *
   * * *
* 5 *
- - - - -
* 7 * * 3

掛け算の記号が抜けているが、掛け算の筆算のつもり。星印が虫食いの場所だ。被乗数の一位の数が7で、答えの一位の数が3だから、7と掛けて一位の数が3になる数を検索した。

Main> [(7,x)| x<-[1..9], 7*x `mod` 10 == 3]
[(7,9)]

がそうなので、乗数の一位の数を9にした。上矢印キーで表示のときの行をコマンドラインに呼び出して、編集して実行した。

Main> dispExp [[-2,-2,-1,-1,7],[-2,-2,3,-1,9],[-3,-3,-3,-3,-3],[-2,-1,0,-1,3],[-2,-1,-1,-1,-2],[-1,5,-1,-2,-2],[-3,-3,-3,-3,-3],[-1,7,-1,-1,3]]
     * * 7
     3 * 9
- - - - -
   * 0 * 3
   * * *
* 5 *
- - - - -
* 7 * * 3

被乗数の一位の数7と乗数の百位の3をかけると21になるので、

     * * 7
     3 * 9
- - - - -
   * 0 * 3
   * * *
* 5 1
- - - - -
* 7 * * 3

被乗数と乗数の百位の3をかけた数の十位の数が5、で下から2が繰り上がっているから被乗数の十位の数と3をかけた数の一位の数は3でないといけない。

Main> [(3,x)| x <- [1..9], 3*x `mod` 10 == 3]
[(3,1)]

     * 1 7
     3 * 9
- - - - -
   * 0 * 3
   * * *
* 5 1
- - - - -
* 7 * * 3

乗数の一位の9と被乗数の17をかけると、153だから計算欄の一行目の十位の数は5だ

     * 1 7
     3 * 9
- - - - -
   * 0 5 3
   * * *
* 5 1
- - - - -
* 7 * * 3

9かける17つまり、153の百位の1とたして0になる数は9だから、被乗数の百位の数は1になる。

Main> [(x,9)| x <- [1..9], x*9 `mod` 10 == 9]
[(1,9)]

     1 1 7
     3 * 9
- - - - -
   1 0 5 3
   * * *
* 5 1
- - - - -
* 7 * * 3

被乗数が117だとわかったので、途中計算欄の3行目は351で確定する。

     1 1 7
   3 * 9
- - - - -
   1 0 5 3
   * * *
3 5 1
- - - - -
* 7 * * 3

答えの千の位が7だから、計算欄の2行目の3桁目は1だ。

     1 1 7
     3 * 9
- - - - -
   1 0 5 3
   1 * *
3 5 1
- - - - -
* 7 * * 3

乗数が117で途中計算の2行目の3桁目が1だから、2行目は117だ。

     1 1 7
     3 1 9
- - - - -
   1 0 5 3
   1 1 7
3 5 1
- - - - -
3 7 * * 3

あとは、答えの虫食いの部分を補うだけ。

     1 1 7
     3 1 9
- - - - -
   1 0 5 3
   1 1 7
3 5 1
- - - - -
3 7 3 2 3

検算してみた。

Main> 117*319
37323

紙で計算したほうが速かったかもしれないが、計算を表示するプログラムを作成する手間をいれた問題解決までのトータルの時間は、かなり短かったのではないだろうか。虫食い算の計算くらいならExcel を使ったほうが速かったかもしれないが、それでも、思考補助手段としての Haskell の有望さが伺われる。


今日の Haskell
Hugs> foldl (flip (:)) [] [1,2,3,4,5]
[5,4,3,2,1]

reverse を foldl で作ってみただけ。
[PR]
by tnomura9 | 2010-11-29 21:27 | Haskell | Comments(0)

畳み込み

Haskell のリスト操作関数のうちとりわけ有用なのが、map、filter、fold(r/l) だ。このうち、map や filter は直感的にわかりやすいのですぐに使えるようになる。map はリストの要素それぞれに関数を作用させた結果のリストを作成するし、filter は述語の条件に合う要素だけのリストを作成する。

Hugs> map (^2) [1..10]
[1,4,9,16,25,36,49,64,81,100]
Hugs> filter even [1..10]
[2,4,6,8,10]

しかし、fold (畳み込み)については、直感的な理解が難しい。リストの総和や階乗を同じ関数で計算できる便利なものだが、どうしてそうなるのかをイメージできないからだ。

Hugs> foldr (+) 0 [1..10]
55
Hugs> foldr (*) 1 [1..10]
3628800

だが、foldr については、リストの (:) 演算子を引数の演算子で置き換えるというイメージで理解することができる。Haskell ではリストは [1, 2, 3, 4, 5] のように表記するが、これは (:) (cons 演算子)を使った、次のような定義のシンタックスシュガーだ。

1 : ( 2 : ( 3 : ( 4 : ( 5 : [] ))))

foldr の動作は上の (:) 演算子を、foldr の引数で置き換え、[] (空リスト)を引数の初期条件(base case) で置き換えてやればいい。たとえば、foldr (+) 0 [1..5] は次のようになる。
foldr (+) 0 [1..5] == 1 + ( 2 + ( 3 + ( 4 + ( 5 + 0 )))) == 15

foldr の引数の演算子が (+) で初期条件が 1 の場合は、

foldr (*) 1 [1..5] == 1 * ( 2 * ( 3 * ( 4 * ( 5 * 1 )))) == 120

となる。これは、次のように再帰的な定義で記述された関数の動作と同じになる。

fact 0 = 1
fact n = n * fact (n-1)

fact 5 == 5 * fact 4
== 5 * ( 4 * fact 3)
== 5 * ( 4 * ( 3 * fact 2))
== 5 * ( 4 * ( 3 * ( 2 * fact 1)))
== 5 * ( 4 * ( 3 * ( 2 * (1 * fact0))))
== 5 * ( 4 * ( 3 * ( 2 * (1 * 1))))
== 120

つまり、foldr は初期条件までを含んで、関数の再帰的定義を一行で表現したものと考えられる。そう考えると、リストの総和や階乗以外の foldr の使い方が見える。例えば max という関数は二つの引数をとり、大きい方を返すが、

Hugs> max 1 2
2

これを foldr の引数にしてやると、リストの中の最大値を見つけてくれる。

Hugs> foldr max 0 [25, 2, 49, 9]
49

この動作は次のように理解できる。

foldr max 0 [25, 2, 49, 9]
== max 25 ( max 2 ( max 49 (max 9 0)))
== max 25 ( max 2 ( max 49 9 ))
== max 25 ( max 2 49 )
== max 25 49
== 49

また、foldr は次のように文字列にも適用できる。

Hugs> foldr (\x y -> x ++ "\n" ++ y) [] ["hello", "world"]
"hello\nworld\n"

foldr が初期値の指定を含めた再帰関数を定義している事が分かれば、コンパクトな記述をするための強力な道具になる。


今日の Haskell
Hugs> takeWhile (<5) [1, 3, 2, 5, 3]
[1,3,2]
Hugs> dropWhile (<5) [1, 3, 2, 5, 3]
[5,3]

後の方に条件に合致する要素があっても、最初に条件を満たさない要素があったところで処理が中断されるところが filter とは異なっている。
[PR]
by tnomura9 | 2010-11-28 09:22 | Haskell | Comments(0)

行列をプリントする

Haskell で [[1,2,3], [4,5,6]] のような行列をプリントするプログラムを作ってみた。最初は普通に行のプリントを繰り返すプログラムにしてみた。
display1.hs

print_row x = putStrLn $ unwords $ map show x
display [] = return ()
display (x:xs) = do
  print_row x
  display xs
  return ()

実行例
Main> display [[1,2,3],[4,5,6]]
1 2 3
4 5 6

これでも動くが、行列を最初に改行を含む文字列にして最後にIOモナドの世界に渡す方式にしてみた。
display2.hs

print_row x = unwords $ map show x
display' [] = ""
display' (x:xs) = print_row x ++ "\n" ++ display' xs
display xs = putStr $ display' xs

少しプログラムが短くなった。しかし、display' (x:xs) は unlines と同じ意味だと気がついたので、unlines を利用したら次のようになった。
display3.hs

display xs = putStr $ unlines $ map (unwords . map show) xs

ひょっとして、Ruby でも同じような記述ができるのではないかと思ったのでやってみた。
irb(main):027:0> def display x
irb(main):028:1>   x.map {|y| y.join(" ")}.join("\n") + "\n"
irb(main):029:1> end
=> nil
irb(main):030:0> print display([[1,2,3],[4,5,6]])
1 2 3
4 5 6
=> nil

表示の全てをIOモナドで記述すると、長くなるし、戻り値がIO型になっているかどうかを気にしなくてはならない。データを通常の関数の世界でこしらえて、それをIOモナドに渡してやる方が記述が簡単になる。また、簡単な再帰的定義の関数は、畳み込みに変更することができる。unlines や unwords は foldr を使った次のような記述でも実現できる。

Hugs> foldr (\x y -> x ++ " " ++ y) "" ["hello", "world"]
"hello world "
Hugs> foldr (\x y -> x ++ "\n" ++ y) "" ["hello", "world"]
"hello\nworld\n"

words や unlines や foldr などの畳み込みを利用すると、再帰的定義の初期条件の記述が省略できるので、プログラムの記述が短くなる。Ruby の join も畳み込みだ。

再帰的定義自体が畳み込みなので、再帰的定義を使ったプログラミングは簡潔になる傾向がある。Rubyにも foldr があれば便利だと思ったら、ここに書いてあった。

module Enumerable
  def foldr(o, m = nil)
    reverse.inject(m) {|m, i| m ? i.send(o, m) : i}
  end

  def foldl(o, m = nil)
    inject(m) {|m, i| m ? m.send(o, i) : i}
  end
end

[1, 2, 3, 4, 5].foldl(:+) #=> 15
[1, 2, 3, 4, 5].foldl(:*) #=> 120

[1, 2, 3, 4, 5].foldr(:-, 0) #=> 3
[1, 2, 3, 4, 5].foldl(:-, 0) #=> -15


今日の Haskell
単語の並べ替え
Hugs> :l List
List> unwords $ sort $ words "this is a pen"
"a is pen this"
[PR]
by tnomura9 | 2010-11-28 01:16 | Haskell | Comments(0)

思考補助電卓としてのHaskell

Haskell が思考補助電卓として使えるかどうかというのは興味のあるところだが、Haskell のプログラムを自然言語に翻訳してみると面白いことが分かる。

例えば定番の階乗の計算だが、
Hugs> fact 5 where fact 0 = 1; fact n = n * fact (n-1)
120

これを自然言語にすると、「n の階乗は、 n と (n-1) の階乗をかけたものに等しく、0の階乗は1である。」となる。

また、素数の計算は、
Hugs> take 10 $ sieve [2..] where sieve (p:xs) = p : sieve [x| x <- xs, mod x p /= 0]
[2,3,5,7,11,13,17,19,23,29]

なので、sieve すなわち「エラトステネスのふるい」の部分を自然言語にすると、「sieve (p:xs) は自然数のリスト (p:xs) から先頭の数と、残りのリスト xs のなかの数 p で割れない数のリストの先頭の数を並べていったリストを作る。」だ。

また、5文字のリストから3文字を取り出す組み合わせを全て列挙するプログラムでは、
Hugs> comb "abcde" 3 where comb _ 0 = [[]]; comb [] _ = []; comb (x:xs) n = map (x:) (comb xs (n-1)) ++ comb xs n
["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"]

comb 関数の再帰的定義の部分を自然言語にすると、「comb n (x:xs) で作られるリストは、引数のリストの先頭の要素を取り出して、その先頭の文字以外のリストから(n-1)個の文字を取り出した文字列の先頭にその文字を付け加えた物のリストと、その先頭の文字以外の文字列から n 個の文字を取り出した文字列のリストを合わせたもの。」

つまり、Haskell のリストと、それを自然言語に直した表現とを見比べると、ほぼ1対1に対応している。つまり、Haskell のプログラムは、自然言語を逐語的にプログラム言語に翻訳したような様相をしめしているのだ。

なぜこうなるのかは分からないが、Haskell が自然言語によって表現したアルゴリズムの逐語的な翻訳を提供するのであれば、Hakell は一種のアルゴリズム電卓と言えるのではないだろうか。Haskell は今のままの仕様でも十分思考補助電卓としての機能があるようだ。


今日の Haskell
乱数の発生
import System.Random

main = do
  gen <- getStdGen
  let ns = randoms gen :: [Int]
  print $ take 10 ns
[PR]
by tnomura9 | 2010-11-26 07:32 | Haskell | Comments(0)

再帰関数とループ

Ruby で1からnまでの数列の和を求める関数を書いてみよう。

$ irb
irb(main):001:0> def sum_l(n, total = 0)
irb(main):002:1>   for i in 1..n
irb(main):003:2>     total += i
irb(main):004:2>   end
irb(main):005:1>   total
irb(main):006:1> end
=> nil
irb(main):007:0> sum_l(10)
=> 55

これを再帰関数で記述するとこうなる。

irb(main):008:0> def sum_r(n)
rb(main):009:1>   if n == 1 then 1
irb(main):010:2>   else n + sum_r(n-1) end
irb(main):011:1> end
=> nil
irb(main):012:0> sum_r(10)
=> 55

インタープリタの内部で何が起きているかを問題にしないなら、sum_l(n) も sum_r(n) も同じように、引数10 をとると戻り値 55 を返してくる。なにも再帰関数による定義はHaskell 固有のものではなく、 Ruby でも同じように記述できる。

Haskell の再帰関数によるループの記述が奇異に見えたとしても、Haskell の問題ではなく、単に自分たちが再帰関数による繰り返しの定義に慣れていないということを意味しているにすぎない。

Haskell を使うのに抵抗を感じなくなるコツは、上のように繰り返しを再帰関数で記述するとどうなるかということを、繰り返し考えることだ。


今日のHaskell
module Face
where

data Face = Face
instance Show Face where
show x = "(~~);"

実行例
Hugs> :l face.hs
Face> print a where a = Face
(~~);

instance の使い方の例。決まりごとが多いので説明が難しいが、自分で定義したデータ型を表示する時の様子をプログラムする方法。
[PR]
by tnomura9 | 2010-11-25 08:27 | Haskell | Comments(0)

Ruby でパターンプログラミング

Ruby の配列操作メソッドに shift がある。これは配列の先頭の要素を取り出すが同時に元の配列からその要素は取り去られる。これを使うとRuby でもHaskell の (x:xs) のようなパターンプログラミングができるのではないかと考えた。下の例の square(x) は引数を2乗する関数だが、mymap(x) は配列を引数にとり、それぞれの要素にsquare(x) を適用した配列を返す。

irb(main):001:0> def square(x)
irb(main):002:1>   x * x
irb(main):003:1> end
=> nil
irb(main):004:0> def mymap(x)
irb(main):005:1>   if x == []
irb(main):006:2>     []
irb(main):007:2>   else
irb(main):008:2*     [square(x.shift)] + mymap(x)
irb(main):009:2>   end
irb(main):010:1> end
=> nil
irb(main):011:0> mymap([1,2,3])
=> [1, 4, 9]

Ruby にはmapメソッドがあるからいらない工夫だが、適用する関数に普通の関数を使うことができる。


今日の Haskell
foldr の簡単な理解の仕方はリストの (:) 演算子を引数の演算子で置き換えると良いらしい。
foldr (+) 0 [1,2,3] == (1 + (2 + (3 + 0))) -- [1,2,3] == (1 : (2 : (3 : []))) の : を + に置き換え。単なる記憶法でなく理論的にもそうらしい。

だんだんネタ切れになってきた。そろそろ沈黙して勉強する時期かも。
[PR]
by tnomura9 | 2010-11-23 21:33 | Haskell | Comments(0)

二次式の因数分解

数値の因数分解の解答がわかったので、二次式の因数分解にチャレンジしてみた。ターゲットになる2次式は、

x ^ 2 + (a + b) x + ab

で、二次の項の計数が1になるものにした。もちろん二次方程式の解の公式を使えば一瞬でとけるが、中学の時のように公式を使わない方法で解けないか考えてみた。

この形の数式を因数分解する場合は、最初に、定数項を二つの因数に分解することから始める。定数項 ab を二つの因数に分解する可能な組み合わせを取り出す関数はどのようなものになるかと考えた。

普通に考えつくのは定数項を素因数分解して、その因数の組み合わせでできる数を全て列挙することだ。たとえば定数項が 6 の場合 6 を素因数分解すると、1 も含めて 6 = 1*2*3 だから、(1,6), (2,3), (3,2), (6,1) というような因数に分解できる。

ところがこの方式だと、素因数の組み合わせを全て考えなければならない、12 = 1*2*2*3 のような場合は、片方の因数を考えるのに、素因数をひとつだけ含む場合、素因数2個の組み合わせ、素因数3個の組み合わせなどを全て考えなければならないので大変な操作になる。プログラム化するにしても、素因数分解する関数、素因数の組み合わせを抽出する関数など複雑になりすぎる。

しばらく考えていたら、n という整数を二つの因数に分解する方法なら、1から初めて2,3、.. と順番に割っていって割り切れたときの数とその数による n の商とのペアを作ればよいことに気がついた。これなら簡単にできる。ちょっと実験してみた。

Hugs> [(n, 6 `div` n)| n <- [1..6], 6 `mod` n == 0]
[(1,6),(2,3),(3,2),(6,1)]

定数項が負の場合を含めて、定数項を2つの因数に分解する関数 splitNum は次のようになる。

splitNum :: Int -> [(Int,Int)]
splitNum n = [(m, n `div` m)| m <- [1..(abs n)], n `mod` m == 0]

試してみた。

Main> splitNum (-6)
[(1,-6),(2,-3),(3,-2),(6,-1)]

定数項が負の場合でもうまくいくようだ。あとは、こうして得られた二つの因数が a + b と一致するかどうかを検出して選び出すだけなので Haskell の得意分野だ。因数分解したときの結果をペアで返す関数 factor を次のソースリストのように記述した。整数では因数分解できない場合も考えて、戻値は Maybe 型にした。splitNum も因数の両方が負数の場合も考えて拡張した。

import List

splitNum :: Int -> [(Int,Int)]
splitNum n = [(m, n `div` m)| m <- (delete 0 [(-(abs n))..(abs n)]), n `mod` m == 0]

factor :: Int -> Int-> Maybe (Int,Int)
factor m n = if factor' == [] then Nothing else Just (factor' !! 0)
  where
    factor' = [(i, j)| (i, j) <- splitNum n, (i + j) == m]

試してみた。

Main> factor 1 (-6)
Just (3,-2)
Main> factor 1 1
Nothing

大成功!!

このプログラムを作っていて気づいたのは、定数項を素因数分解してからその因数の組み合わせで定数項を分解するというやり方は、分かりづらいのではないかということだ。例えば 6 を因数に分解するときは次のように、1から6までの数字を並べて、そのうちの6の因数に丸をつけ、その下にもう片方の因数を書くほうが初めて学ぶ人間にも分かりやすいのではないだろうか。

1 2 3 4 5 6
6 3 2 * * 1

これなら、因数分解や組み合わせを全て列挙するという知識がなくても、簡単に定数項を二つの因数に分解できる。Haskell のプログラムを作成することによって、逆に人間の思考のありかたの新しい面を発見できるのが面白い。


今日の Haskell
Haskell では負の数は必ずカッコで括らなければならない。
Main> 1 + (-2)
-1
[PR]
by tnomura9 | 2010-11-23 09:27 | Haskell | Comments(0)

素因数分解

Haskell で素因数分解をどう記述するのかネットで調べてみた。いろいろな解答があったが、一番わかりやすかったのがこれ。

factors :: Integer -> [Integer]
factors n = [x | x <- [1..n], n `mod` x == 0]

factorization :: Integer -> [Integer]
factorization 1 = []
factorization x = v : factorization (x `div` v)
  where
    v = (factors x) !! 1

imHo さんの 素因数分解を求めるgolf という記事に掲載されていた。

上のプログラムで factors n は、n の約数のリスト。 (factors n) !! 1 は約数のうちの1以外の最小のもの。
Main> factors 12
[1,2,3,4,6,12]
Main> (factors 12) !! 1
2

factorization は、n を最小の約数で次々割っていって、n が 1 になったら終了する。簡単なアルゴリズムだが、600851475143 の素因数分解もすぐに結果が出た。
Main> factorization 600851475143
[71,839,1471,6857]

すごい!!


今日の Haskell
Main> minimum [2,0,1,0,11,22]
0
Main> maximum [2,0,1,0,11,22]
22

こういう小道具をたくさん道具箱に入れておくのが、Haskell 職人への道だろう。
[PR]
by tnomura9 | 2010-11-23 02:50 | Haskell | Comments(0)