【Haskell】Stringの連結でハマった

ここ数日、「すごいHaskell楽しく学ぼう!」でHaskellしてます。関数型プログラミングを勉強してみたかった。

第4章を少し進めて再帰の考え方がわかってきたため、復習としてFizzBuzzを。

fizzbuzz :: Int -> [String]
fizzbuzz n | n < 1 = []
fizzbuzz n = fizzbuzz (n-1) ++ [(fb n)]
    where fb n
            | n `mod` 15 == 0 = "FizzBuzz"
            | n `mod` 3 == 0 = "Fizz"
            | n `mod` 5 == 0 = "Buzz"
            | otherwise = show n

Intを引数にとり、1から引数で指定した数までをFizzBuzzします。

引数nを関数fbでFizzBuzz文字へ変換し、その先頭へ再帰でまたFizzBuzz文字をつなげています。

本題ですが、3行目のリストの連結で2時間ほどハマっていました。

リストの連結には2つ方法がある

リストの連結には2つの方法があります。 : を使う方法と ++ を使う方法です。

Prelude> let x = [3,5,7]
Prelude> x ++ [5, 3]
[3,5,7,5,3]

Prelude> 5 : x
[5,3,5,7]
Prelude> 7 : 5 : x
[7,5,3,5,7]
Prelude>

これだけでは違いがわかりにくいですね。

これではどうでしょう。

Prelude> [5,3] ++ x
[5,3,3,5,7]
Prelude> x : 5

<interactive>:31:1: error:
    • Non type-variable argument in the constraint: Num [[t]]
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall t. (Num [[t]], Num t) => [[t]]
Prelude> x : 5 : 3

<interactive>:32:1: error:
    • Non type-variable argument in the constraint: Num [[t]]
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall t. (Num [[t]], Num [t], Num t) => [[t]]
Prelude>

++演算子は2つの値を入れ替えても動作します。

一方 : 演算子はエラーが発生しました。

演算子の定義を確認してみましょう。

Prelude> :t (++)
(++) :: [a] -> [a] -> [a]

Prelude> :t (:)
(:) :: a -> [a] -> [a]

つまり、

  • ++演算子は「2つのリストを連結する」
  • : 演算子は「後者のリストの先頭に前者を追加する」

というように考えられます。似ているようで似てなかった。

FizzBuzz書いているときは当該の行を

fizzbuzz n = (fizzbuzz (n-1)) : (fb n)

としておりました。エラーメッセージでも型の齟齬が吐かれていましたが、少しややこしいものでした。

キョウリョクナカタスイロン

先程の一行を型に注目するとこうなります。

fizzbuzz n = [String] : String

またエラーメッセージではこの行について

  • 「(fizzbuzz (n-1))の返り値の型、[[String] ]になってんぞ。[String]じゃね?」

  • 「(fb n)の返り値の型、[Char]になってんぞ。[[String] ]じゃね?」

と、強力な型推論のおかげ(せい)で余計に惑わされました。

以上です。

まとめ

haskellを触り始めて1日目、エラーにハマったおかげでかえって満足です。エラーメッセージとがっつり対峙できたため今後はスルスルと進めていきたいです。