#はじめに
現在、書籍「すごいHaskellたのしく学ぼう」(第1版第4刷)を読んでHaskellを勉強しています。
Haskell入門の良書として有名なようなのですが2012年発行で多少情報が古くなっているので、今からこの書籍を読もうという方はこちらの記事などを参考にすると良いと思います。
2017年に「すごいHaskellたのしく学ぼう」を読む
この書籍の 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 明示的な型宣言
で関数の明示的な型宣言が出る以前では、サンプルコードで関数の型宣言がされていませんが、これはエラーが出ます。
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
エラーですね。
doubleMe :: Num a => a -> a -- <-- 追記
doubleMe x = x + x
このように型宣言を追記しましょう。
#第2章
とくになし
#第3章
##3.1 パターンマッチ
パターンマッチで、任意の値に合致するパターンとしてx
やmyNumber
などを使っているのに、これを変数として処理の中で使用していない場合エラーが出ます。
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
エラーですね。
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
というサンプル関数(同名の関数が複数回登場する。)で累乗の演算子として^
が使用されていますが、このままではエラーが出ます。
*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ので、ここで^
を使用するとエラーとなります。
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 #- }
オプションではエラーとして弾かれます。
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!"
^
を**
に置換しましょう。あるいは、
weight / height ^ (2 :: Integer)
このように指数2
に型注釈を付けましょう。
これ以降も同様のエラー箇所があります。
#第4章
とくになし
#第5章
とくになし
#第6章
とくになし
#第7章
##7.3 レコード構文
レコード構文登場前の自作Person
型のフィールド値出力関数群(p. 118)は、そのままではエラーが出ます。
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.
関数名と変数名が同一で重複していることがエラーの原因です。
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
というサンプル関数はそのままではエラーが出ます。
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
型でない値が渡されてしまった場合へのパターンマッチが無いのが原因です。
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 [()]
など、適切な型宣言をしなければエラーが出ます。