25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

HaskellAdvent Calendar 2019

Day 7

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

Last updated at Posted at 2019-12-22

#はじめに

現在、書籍「すごいHaskellたのしく学ぼう」(第1版第4刷)を読んでHaskellを勉強しています。

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

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

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

#環境

macOSで、Homebrewhaskell-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エラーですね。

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

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

#第2章

とくになし

#第3章

##3.1 パターンマッチ

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


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というサンプル関数(同名の関数が複数回登場する。)で累乗の演算子として^が使用されていますが、このままではエラーが出ます。


*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 [()]
など、適切な型宣言をしなければエラーが出ます。

  1. Haskell-演算子, Capm Network

25
20
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?