HaskellでFizzBuzz問題を解く(2)

 FizzBuzzも解けない開発者だというのはやはり問題なので、前回http://d.hatena.ne.jp/marony0607/20111023/1319331471に続きひたすら頑張ります。前回、最初の5しか"Buzz"に変換されなかったのは、myElem関数の中の"x > y"の不等号が逆だったからでした。"x < y"に書き直すと、全ての5の倍数は"Buzz"に変更されました。は、恥ずかしい!!

 そして、続きです。5の倍数さえ出来ちゃえば後は同じことを3の倍数と15の倍数に対しても繰り返せば良いだけです。3の倍数を変換する関数gと15の倍数を変換する関数hを定義して"map f x"で返ってきたリストをgにもhにも渡せばいいだけでしょ。まぁ、15の倍数から先に処理しないとうまくいかないけど。

 今の時点でのソースです。

myElem :: Int -> [Int] -> Bool 
myElem x [] = False
myElem x (y:ys) = if x < y then False else if x == y then True else myElem x ys
 
f :: Int -> String
f x = if x `myElem` [5, 10..] then "Buzz" else show x
 
fizzBuzz :: [Int] -> [String]
fizzBuzz x = map f x
 
main = putStrLn $ show $ fizzBuzz [1..100]

 それでは、3の倍数と15の倍数に変換する関数も作っちゃって連続で呼んじゃいましょう。
 次のように関数を定義しました。
f :: Int -> String 
f x = if x `myElem` [15, 30..] then "FizzBuzz" else show x
 
g :: Int -> String
g x = if x `myElem` [5, 10..] then "Buzz" else show x
 
h :: Int -> String
h x = if x `myElem` [3, 6..] then "Fizz" else show x
 あれ?なんかおかしい…
 あ゛あ゛あ゛
 えーとですね、関数fでIntからStringに変換するので、最初にIntのリスト[Int]がStringのリスト[String]に変換されるわけです。そしたら次の関数gに渡せない… だって、gの引数はIntで渡されるのはString…

 困りました。IntもStringも入れられるリストがあれば良いんだけど、Haskellのリストには一つの型しか入れられません。調べてみるとHaskellには代数データ型というのがあり、これを使えばIntとStringを同時に入れられるようです。Cのunionみたいなものでしょうか?
 使ってみましょう。

data Elem = I Int | S String

このElemという型にはIntもStringも入れられるはずです。それっぽいところを全てElem型にしてみました。
data Elem = I Int | S String 
myElem :: Elem -> [Elem] -> Bool
myElem x [] = False
myElem x (y:ys) = if x < y then False else if x == y then True else myElem x ys
 
f :: Elem -> Elem
f x = if x `myElem` (map I [15, 30..])  -- "FizzBuzz"
        then "FizzBuzz"
        else x
 
g :: Elem -> Elem
g x = if x `myElem` (map I [5, 10..])  -- "Buzz"
        then "Buzz"
        else x
 
h :: Elem -> Elem
h x = if x `myElem` (map I [3, 6..])  -- "Fizz"
        then "Fizz"
        else x
 
fizzBuzz :: [Elem] -> [Elem]
fizzBuzz x = map h (map g (map f x))
 
main = putStrLn $ show $ fizzBuzz [1..100]
そしてコンパイル
$ ghc fizzbuzz 
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
fizzbuzz.hs:5:24:
    No instance for (Ord Elem)
      arising from a use of `<'
    Possible fix: add an instance declaration for (Ord Elem)
    In the expression: x < y  
    In the expression:
      if x < y then False else if x == y then True else myElem x ys
    In an equation for `myElem':
        myElem x (y : ys)
          = if x < y then False else if x == y then True else myElem x ys
 本当はもっと一杯エラーが出ましたが、まず最初のエラーを解決します。5行目の"x < y"でElemにはそんな演算子(関数)ないよ、と言ってますね。どうしましょう…
 数値に変換すれば良いんですが、文字列の場合もあります。一度、"Fizz", "Buzz", "FizzBuzz"に変換した場合には、xに文字列が入っています。まずは引数が数値のだけの時と文字列だけの時で処理を分けましょう。この「処理」を分けましょうって思うのが既に手続き脳な気もしますが…
 調べてみるとパターンマッチングっていうのを使うみたいです。
myElem :: Elem -> [Elem] -> Bool 
myElem (S x) y          = False
myElem (I x) ((I y):ys) = internal_elem x ((I y):ys)
 2行目はxが文字列の場合の定義です(多分)。で、3行目がxもリストyの先頭も数値の時の定義です(多分)。そして、両方とも数値だった時の場合だけ呼ばれるinternal_elemという関数を定義しました。
internal_elem :: Int -> [Elem] -> Bool 
internal_elem x [] = False
internal_elem x (y:ys) | x <  y  = False
                       | x == y  = True
                       | True    = internal_elem x ys
 今度はガード節というのを使ってみました。パターンは型とかから呼ぶ関数を判断して、ガード節は任意の式から実行する式を決めるのかな?さらにcaseとかもありますね。ガード節の最後に無条件で実行する書き方が分からなかったので、Trueって書いときました。

 では、コンパイル

$ ghc fizzbuzz 
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
fizzbuzz.hs:11:31:
    Couldn't match expected type `Int' with actual type `Elem'
    In the second argument of `(<)', namely `y'
    In the expression: x < y
    In a stmt of a pattern guard for
                   an equation for `internal_elem':
      x < y
 またもや大量にエラー…
 最初のエラーを見てみます。また似たようなエラーですね。xは整数ですがyはElem型なので"<"で比較できないっていうエラーです。仕方がないのでElem型を整数に変換する関数を書いて呼び出すことにしました。 Elem型にメソッド定義する方法知らないし。
internal_elem :: Int -> [Elem] -> Bool 
internal_elem x [] = False
internal_elem x (y:ys) | x <  (elem_to_int y)  = False
                       | x == (elem_to_int y)  = True
                       | True                  = internal_elem x ys
 
elem_to_int :: Elem -> Int
elem_to_int (I x) = x
すると、
$ ghc fizzbuzz 
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
fizzbuzz.hs:21:14:
    Couldn't match expected type `Elem' with actual type `[Char]'
    In the expression: "FizzBuzz"
    In the expression:
      if x `myElem` (map I [15, 30 .. ]) then "FizzBuzz" else x
    In an equation for `f':
        f x = if x `myElem` (map I [15, 30 .. ]) then "FizzBuzz" else x
 今度は文字列をElem型に変換できないというエラー。素直に変換してあげます。
f :: Elem -> Elem 
f x = if x `myElem` (map I [15, 30..])  -- "FizzBuzz"
        then S "FizzBuzz"
        else x
 
g :: Elem -> Elem
g x = if x `myElem` (map I [5, 10..])  -- "Buzz"
        then S "Buzz"
        else x
 
h :: Elem -> Elem
h x = if x `myElem` (map I [3, 6..])  -- "Fizz"
        then S "Fizz"
        else x
いい加減嫌になってきました。またエラーです。
$ ghc fizzbuzz 
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
fizzbuzz.hs:37:19:
    No instance for (Show Elem)
      arising from a use of `show'
    Possible fix: add an instance declaration for (Show Elem)
    In the expression: show
    In the second argument of `($)', namely
      `show $ fizzBuzz [1 .. 100]'
    In the expression: putStrLn $ show $ fizzBuzz [1 .. 100]
 
fizzbuzz.hs:37:35:
    No instance for (Enum Elem)  
      arising from the arithmetic sequence `1 .. 100'
    Possible fix: add an instance declaration for (Enum Elem)
    In the first argument of `fizzBuzz', namely `[1 .. 100]'
    In the second argument of `($)', namely `fizzBuzz [1 .. 100]'
    In the second argument of `($)', namely
      `show $ fizzBuzz [1 .. 100]'
 
fizzbuzz.hs:37:39:
    No instance for (Num Elem)
      arising from the literal `100'
    Possible fix: add an instance declaration for (Num Elem)
    In the expression: 100
    In the first argument of `fizzBuzz', namely `[1 .. 100]'
    In the second argument of `($)', namely `fizzBuzz [1 .. 100]'
 これ苦労しました。ElemにはShowメソッドがないんで呼べないし、FizzBuzzの引数は[Elem]なのに[Int]渡しちゃってるし…と色々直してこんな感じになりました。
data Elem = I Int | S String 
myElem :: Elem -> [Elem] -> Bool
myElem (S x) y          = False
myElem (I x) ((I y):ys) = internal_elem x ((I y):ys)
 
internal_elem :: Int -> [Elem] -> Bool
internal_elem x [] = False
internal_elem x (y:ys) | x <  (elem_to_int y)  = False
                       | x == (elem_to_int y)  = True
                       | True                  = internal_elem x ys
 
elem_to_int :: Elem -> Int
elem_to_int (I x) = x
 
elem_to_string :: Elem -> String
elem_to_string (I x) = show x
elem_to_string (S x) = x
 
f :: Elem -> Elem
f x = if x `myElem` (map I [15, 30..])  -- "FizzBuzz"
        then S "FizzBuzz"
        else x
 
g :: Elem -> Elem
g x = if x `myElem` (map I [5, 10..])  -- "Buzz"
        then S "Buzz"
        else x
 
h :: Elem -> Elem
h x = if x `myElem` (map I [3, 6..])  -- "Fizz"
        then S "Fizz"
        else x
 
fizzBuzz :: [Elem] -> [Elem]
fizzBuzz x = map h (map g (map f x))
 
main = putStrLn $ unwords result
    where result = map elem_to_string fizzbuzz
          fizzbuzz = fizzBuzz $ map I [1..100]
 そしてコンパイル!!
$ ghc fizzbuzz 
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
Linking fizzbuzz.exe ...
 じゃじゃーーーーん!!!!!!
 通りました。やったね。
 早速実行。
$ fizzbuzz 
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
 おおおおおおおお。全部確認するの面倒だけど多分できてる。やっと完成。FizzBuzzできるプログラマに進化。
 
 でもですね。分かってます。きったないコードですね…
 なんとかしたいけど、知識が…
 それぞれの関数で作ったリストをマージとかどうだろう? 新しい型を定義するんじゃなくてタプルのリストとして扱うとか。
 どちらにしても要修正です。もっと勉強してきます。