2020年に「すごいHaskellたのしく学ぼう」を { -# OPTIONS -Wall -Werror #- } を付けて読む

#はじめに

現在、書籍「すごいHaskellたのしく学ぼう」（第１版第４刷）を読んでHaskellを勉強しています。

Haskell入門の良書として有名なようなのですが2012年発行で多少情報が古くなっているので、今からこの書籍を読もうという方はこちらの記事などを参考にすると良いと思います。
[2017年に「すごいHaskellたのしく学ぼう」を読む](https://qiita.com/Aruneko/items/e72f7c6ee49159751cba)

この書籍の p. 37 の訳注で、`{-# OPTIONS -Wall -Werror #-}` という記述を `.hs`ファイル（Haskellのコードを記述するファイル）の先頭行に付け、通常より厳密な警告を有効にすることが提案されています。こうすると`:l hogehoge(ファイル名)`で`.hs`ファイルをコンパイル&ロードする段階で通常より厳密な警告が出るので、より厳密なコーディングが出来ます。

ところが、このオプションを付けたままこの書籍のサンプルコードを試していると、このオプションによってエラーが吐かれるものが有ったので、自分用&他の学習者用にメモしていこうと思います。

#環境

macOSで、`Homebrew`で`haskell-stack`パッケージを入れています。

`stack:2.1.3 x86_64`
`GHCi:8.6.5`

#第1章

`第2章/2.1 明示的な型宣言`で関数の明示的な型宣言が出る以前では、サンプルコードで関数の型宣言がされていませんが、これはエラーが出ます。

```haskell

Prelude> :l baby
[1 of 1] Compiling Main             ( baby.hs, interpreted )

baby.hs:4:1: error: [-Wmissing-signatures, -Werror=missing-signatures]
    Top-level binding with no type signature:
      doubleMe :: Num a => a -> a
  |
4 | doubleMe x = x + x
  | ^^^^^^^^
Failed, no modules loaded.
```

`Top-level binding with no type signature`エラーですね。

```baby.hs
doubleMe :: Num a => a -> a -- <-- 追記
doubleMe x = x + x
```

このように型宣言を追記しましょう。

#第2章

とくになし

#第3章

##3.1 パターンマッチ

パターンマッチで、任意の値に合致するパターンとして`x`や`myNumber`などを使っているのに、これを変数として処理の中で使用していない場合エラーが出ます。

```haskell

Prelude> :l baby
[1 of 1] Compiling Main             ( baby.hs, interpreted )

baby.hs:38:7: error: [-Wunused-matches, -Werror=unused-matches]
    Defined but not used: ‘x’
   |
38 | lucky x = "Sorry, you're out of luck, pal!"
   |       ^
Failed, no modules loaded.
```

`Defined but not used`エラーですね。

```baby.hs
lucky :: Int -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
-- lucky x = "Sorry, you're out of luck, pal!"
lucky _ = "Sorry, you're out of luck, pal!"
```

このように変数名アンダースコア（ _ ）を使いましょう。
同様のエラーは他の`as パターン`などでも発生します。

##3.2 場合分けして、きっちりガード！

p. 42 の`bmiTell`というサンプル関数（同名の関数が複数回登場する。）で累乗の演算子として`^`が使用されていますが、このままではエラーが出ます。

```haskell

*Main> :l baby
[1 of 1] Compiling Main             ( baby.hs, interpreted )

baby.hs:70:14: error: [-Wtype-defaults, -Werror=type-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Integral b0) arising from a use of ‘^’ at baby.hs:70:14-23
        (Num b0) arising from the literal ‘2’ at baby.hs:70:23
    • In the second argument of ‘(/)’, namely ‘height ^ 2’
      In the first argument of ‘(<=)’, namely ‘weight / height ^ 2’
      In the expression: weight / height ^ 2 <= 18.5
   |
70 |   | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
   |              ^^^^^^^^^^
```

~~`^`はInt型の累乗を計算し、`**`はDouble型の累乗を計算する[^1]ので、ここで`^`を使用するとエラーとなります。~~

```haskell

Defaulting the following constraints to type ‘Integer’
```

*以下の束縛を`Integer`型にした*、と言っています。

`^`の型は`(^) :: (Integral b, Num a) => a -> b -> a`
`**`の型は`(**) :: Floating a => a -> a -> a`
です。そのため、`^`では指数に実数型は使えず、`bmiTell`では指数の`2`に型指定がないため`type defaulting`という仕組みによって`Integer`型に型指定されますが、これはプログラマの指定不足によるため、` { -# OPTIONS -Wall -Werror #- } `オプションではエラーとして弾かれます。

```haskell

bmiTell :: Double -> Double -> String
bmiTell weight height
  -- | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
  | weight / height ** 2 <= 18.5 = "You're underweight, you emo, you!"
  -- | weight / height ^ 2 <= 25.0 = "You're supposedly normal.\
  | weight / height ** 2 <= 25.0 = "You're supposedly normal.\
      \ Pffft, I bet you're ugly!"
  -- | weight / height ^ 2 <= 30.0 = "You're fat!\
  | weight / height ** 2 <= 30.0 = "You're fat!\
      \ Lose some weight, fatty!"
  | otherwise                    = "You're a whale, congratulations!"
```

`^`を`**`に置換しましょう。あるいは、

```haskell

weight / height ^ (2 :: Integer)
```

このように指数`2`に型注釈を付けましょう。
これ以降も同様のエラー箇所があります。

#第4章

とくになし

#第5章

とくになし

#第6章

とくになし

#第7章

##7.3 レコード構文

レコード構文登場前の自作`Person`型のフィールド値出力関数群（p. 118）は、そのままではエラーが出ます。

```haskell

Prelude> :l baby_7.hs 
[1 of 1] Compiling Main             ( baby_7.hs, interpreted )

baby_7.hs:40:17: error: [-Wname-shadowing, -Werror=name-shadowing]
    This binding for ‘age’ shadows the existing binding
      defined at baby_7.hs:40:1
   |
40 | age (Person _ _ age _ _ _) =  age
   |                 ^^^

baby_7.hs:43:22: error: [-Wname-shadowing, -Werror=name-shadowing]
    This binding for ‘height’ shadows the existing binding
      defined at baby_7.hs:43:1
   |
43 | height (Person _ _ _ height _ _) = height
   |                      ^^^^^^

baby_7.hs:49:26: error: [-Wname-shadowing, -Werror=name-shadowing]
    This binding for ‘flavor’ shadows the existing binding
      defined at baby_7.hs:49:1
   |
49 | flavor (Person _ _ _ _ _ flavor) = flavor
   |                          ^^^^^^
Failed, no modules loaded.
```

関数名と変数名が同一で重複していることがエラーの原因です。

```haskell

data Person = Person String String Int Float String String
  deriving (Show)

firstName :: Person -> String
firstName (Person firstname _ _ _ _ _) = firstname

lastName :: Person -> String
lastName (Person _ lastname _ _ _ _) = lastname

age :: Person -> Int
-- age (Person _ _ age _ _ _) =  age
age (Person _ _ age' _ _ _) =  age'

height :: Person -> Float
-- height (Person _ _ _ height _ _) = height
height (Person _ _ _ height' _ _) = height'

phoneNumber :: Person -> String
phoneNumber (Person _ _ _ _ number _) = number

flavor :: Person -> String
-- flavor (Person _ _ _ _ _ flavor) = flavor
flavor (Person _ _ _ _ _ flavor') = flavor'
```

適当に名称変更すれば大丈夫です。

##7.7 再帰的なデータ構造

p. 140の`treeInsert`というサンプル関数はそのままではエラーが出ます。

```haskell

Prelude> :l baby_7.hs 
[1 of 1] Compiling Main             ( baby_7.hs, interpreted )

baby_7.hs:188:1: error: [-Wincomplete-patterns, -Werror=incomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘treeInsert’:
        Patterns not matched: _ (Node _ _ _)
    |
188 | treeInsert x EmptyTree = singleton x
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
Failed, no modules loaded.
```

`Pattern match(es) are non-exhaustive`、つまりパターンマッチが全ての場合をカバー出来ていないというエラーです。
第2引数に`Tree`型でない値が渡されてしまった場合へのパターンマッチが無いのが原因です。

```haskell

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
  | x == a = Node x left right
  | x < a  = Node a (treeInsert x left) right
  | x > a  = Node a left (treeInsert x right)
treeInsert x _ = singleton x -- <-- どちらか追記
-- treeInsert _ _ = error "第2引数にはTree型の値を渡してください。" -- <-- どちらか追記
```

パターンマッチを追加し、その時はerrorでランタイムエラーを生成するなど何か挙動を指定しましょう。

#ここ以降の注意

###第8章以降、ghciでの対話式実行だけでなく、ghcなどによってプログラムをコンパイルして実行ファイルを生成する方法も出てきます。

###Haskellの開発環境を整えようとしてHaskellを入れ直したりしていたので、僕の環境のGHCのバージョンが変わりました。

`stack:2.1.3 x86_64 hpack-0.31.2`
`GHCi:8.8.2`

#第8章

##`main`を使う時

`main`についても
`main :: IO ()`
や
`main :: IO [()]`
など、適切な型宣言をしなければエラーが出ます。

[^1]: [Haskell-演算子, Capm Network](http://capm-network.com/?tag=Haskell-演算子)