すごいHaskell三章

パターンマッチ

引数の値、構造でマッチさせる。
if文で書くと

sayMeIf :: Int -> String
sayMeIf x = if x == 1 then "One!"
     else if x == 2 then "Two!"
     else if x == 3 then "Three!"
     else if x == 4 then "Four!"
     else if x == 5 then "Five!"
     else "Not between 1 and 5"

となるのを、パターンマッチで書くと

sayMe :: Int -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe x = "Not between 1 and 5"

さっぱりしてかっこいい。
パターンは網羅するように書く必要がある(sayMe x みたいなのがいる)

-Wallオプションだが

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

baby.hs:13:11: Warning: Defined but not used: `x'

baby.hs:26:7: Warning: Defined but not used: `x'

baby.hs:38:7: Warning: Defined but not used: `x'

<no location info>:
Failing due to -Werror.

Failed, modules loaded: none.

パターンマッチ中のxに反応してるんだけど、
これは何が原因なんだろう。
オプションをソースコードから消すとWarningが出なくなる。

"警告と正気度チェックのためのオプション" http://www.kotha.net/ghcguide_ja/7.0.4/options-sanity.html

あー -Werror は全部の警告をエラーにするのか。
このWarningは、定義したけど使ってない変数があるってことなんだろうけど、ここで使ってるのはパターンマッチの「その他の場合」用の引数なんだけどな。
分かった。変数じゃなくてプレースホルダ(_)にすればいいんだ。使ってないのに宣言するなってことだわな。
上の例だと、次のようになる。

sayMe :: Int -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe _ = "Not between 1 and 5"

最後の x を _ にした。


-Wallしてると、型宣言されてない関数もWarningが出るみたい。


あと、型クラスを型宣言に使っちゃってエラーが出た。

doubleMe :: Num -> Num
doubleMe x            = x + x

正しいのはこう。

doubleMe :: (Num => a) a -> a
doubleMe x            = x + x


2つ以上の型クラスのインスタンスになってる場合はこう書くらしい。

doubleSmallNumber :: (Ord a, Num a) => a -> a
doubleSmallNumber x   = if x > 100
                        then x
                        else x * 2

構造のパターンマッチ

タプルのパターンマッチ

addVectorsStr :: (String, String) -> (String, String) -> (String, String)
addVectorsStr (sx1, sx2) (sy1, sy2) = (sx1 ++ sy1, sx2 ++ sy2)
ghci> addVectorsStr ("ABC", "ZYX") ("DEF", "WVU")
("ABCDEF","ZYXWVU")


トリプルの一つ目の要素を取り出す関数

first :: (a, b, c) -> a
first (x, _, _) = x

こういうのって、単純だけどパッと思いつかなさそうな雰囲気がプンプンしてる。


リストのパターンマッチ。先頭の要素を : を使って取り出す。

head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x

複数の変数に束縛する時は丸括弧で囲む必要がある。囲まないとエラー

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

baby.hs:5:1: Parse error in pattern: head'
Failed, modules loaded: none.

これだと構文が多分間違ってたんだろうな…ってとしか分からんな。

asパターン

要素にパターンマッチしつつ、値全体もバインドする。
本に載ってた定義だと、allがPreludeで使われてるって警告と、xsが利用されてないって警告が出るので書きなおした。


警告:

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

baby.hs:5:13:
    Warning: This binding for `all' shadows the existing binding
               imported from Prelude

baby.hs:5:20: Warning: Defined but not used: `xs'

<no location info>:
Failing due to -Werror.

Failed, modules loaded: none.

書きなおした:

firstLetter :: String -> String
firstLetter "" = "Empty string, woops!"
firstLetter str@(x:_) = "The first letter of " ++ str ++ " is " ++ [x]

ガード

パターンマッチとガードの区別がついてなかったが、パターンマッチは構造で場合分けする、ガードは真理値式で場合分けするという違い。パターンマッチさせたあと、そのパターンに対してガードでさらに場合分け出来る。
パターンマッチは構造だけじゃなくて、値でも場合分け出来る。もちろんガードでもできるが、大なり小なりを使った場合などはガードでしかできない。
if文で書き換えることも出来る。が、ガードのほうが見やすい。

bmiTell :: Double -> String
bmiTell bmi
    | bmi <= 18.5 = "You're underweitght, you em, you!"
    | bmi <= 25.0 = "You're supposedly normal. \
                    \  Pffft, I bet you're ugly!"
    | bmi <= 30.0 = "You're fat! Lose some weght, fatty!"
    | otherwise   = "You're a whale, congraturlations!"
bmiTellIf :: Double -> String
bmiTellIf bmi = 
    if      bmi <= 18.5 then "You're underweitght, you em, you!"
    else if bmi <= 25.0 then "You're supposedly normal. \
                    \  Pffft, I bet you're ugly!"
    else if bmi <= 30.0 then "You're fat! Lose some weght, fatty!"
    else "You're a whale, congraturlations!"

こうして見ると、値だけで場合分けしたいのにifやらelseやらが入ってくるのに違和感がある。

Defaulting警告

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

コンパイル(-Wallで)すると、以下の警告が出る。

[1 of 1] Compiling Main             ( baby.hs, interpreted )

baby.hs:5:25:
    Warning: Defaulting the following constraint(s) to type `Integer'
               (Integral b0) arising from a use of `^' at baby.hs:5:25
               (Num b0) arising from the literal `2' at baby.hs:5:27
    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

baby.hs:6:25:
    Warning: Defaulting the following constraint(s) to type `Integer'
               (Integral b0) arising from a use of `^' at baby.hs:6:25
               (Num b0) arising from the literal `2' at baby.hs:6:27
    In the second argument of `(/)', namely `(height ^ 2)'
    In the first argument of `(<=)', namely `(weight / (height ^ 2))'
    In the expression: (weight / (height ^ 2)) <= 25.0

baby.hs:8:25:
    Warning: Defaulting the following constraint(s) to type `Integer'
               (Integral b0) arising from a use of `^' at baby.hs:8:25
               (Num b0) arising from the literal `2' at baby.hs:8:27
    In the second argument of `(/)', namely `(height ^ 2)'
    In the first argument of `(<=)', namely `(weight / (height ^ 2))'
    In the expression: (weight / (height ^ 2)) <= 30.0

<no location info>:
Failing due to -Werror.

Failed, modules loaded: none.

"2010-01-14 - Maeの(Mae向きな)日記" http://d.hatena.ne.jp/rahaema/20100114

Defaultingって仕組みで自動で型が決まってるかららしい。
でもこれどうやって型を指定したらいいんだろう。

bmiTell :: Double -> Double ->String
bmiTell weight height
    | weight / height ^ (2 :: Integer)  <= 18.5 = "You're underweitght, you em, you!"
    | weight / height ^ (2 :: Integer) <= 25.0 = "You're supposedly normal. \
                    \  Pffft, I bet you're ugly!"
    | weight / height ^ (2 ::Integer) <= 30.0 = "You're fat! Lose some weght, fatty!"
    | otherwise                   = "You're a whale, congraturlations!"

こうらしい。あの2が自動でIntegerになってたわけ。IntかIntegerかDoubleか…みたいな感じなのかな。^の引数がDefaultingでIntegerになってたっぽいけど。

where

計算の中間結果に名前をつける。ガードの後に置くと、ガードのどこからでも値が見えるようになる。定数っぽい使い方もできる。
パターンの後に置くと、パターンの中でだけ共有される。関数本体では共有されない。
whereの中でもパターンマッチを使える……どんどん定義が後ろに下がるかんじ。これは関数の引数のところでパターンマッチしたほうがいいって書いてる。


whereブロックで関数定義+リスト内包。これいきなり書けって言われて書けそうにないなあ……。ちなみに高さと底辺の長さのペアを受け取って、三角形の面積のリストを返す。

calcTriangleArea :: [(Double, Double)] -> [Double]
calcTriangleArea xs = [area height base| (height, base) <- xs]
    where area height base = height * base / (2 :: Double)

let

whereは束縛。letは式。letは式だから関数の返り値(というか)定義のところに書ける。基本的にどこでも書けるっぽい。

ghci> 4 * (let a = 8; b = 19 in a * 18) + 32
608

ぱない
whereは束縛を式の後に書くんだけど、letは束縛を書いてから式を書く。なにげにwhereの方が、目的の式が見えるって意味で読みやすいかもしれない。

ghci> let (a, b, c) = (1, 2, 3) in a + b + c
6

ぱない
相変わらずパターンマッチはぱっと見てしっくりこない。


letの束縛はその直後のinの中でしか使えない。
と思ったらリスト内包での使い方はinが出ててこない。

calcTriangleAreaLet :: [(Double, Double)] -> [Double]
calcTriangleAreaLet xs = [area | (h, b) <- xs, let area = h * b / (2 :: Double), area > 20.0]

ここでの (h, b) <- xsの部分はジェネレータと呼んで、letの束縛より前で定義されてるから変数areaは呼べないらしい。リスト内包の右の部分を前から順に読むと、xsからペアを取り出し、areaを計算し、20.0以下のものをはじく。ようになってるっぽい。

case

関数定義の時の引数のパターンマッチはcase式のシンタックスシュガー。
case式はどこでも使える。case で引数を取って、 of でマッチさせる。


こういう書き方ってどうなんだろう、可読性的に。

describeList :: [a] -> String
describeList ls = "The list is "
                  ++ case ls of [] -> "empty."
		                [x] -> "a singleton list"
				xs -> "a longer list."

もう一つ載ってるのはこれ。

describeListWhere :: [a] -> String
describeListWhere ls = " The list is " ++ what ls
    where what [] = "empty"
          what [x] = "a singleton list"
	  what xs = "a longer list."

好みの問題な気がするけど、こっちのほうが読みやすい気がする。
というかこの関数自体、パターンマッチと文字列生成を一遍にやってるのがなんか違和感ある。全然小さい関数だからいいのかもしれないけど。


うおーすごい時間かかった。次はいよいよ盛り上がりどころの再帰だ。


この本って教科書じゃないから練習問題がないのな。そのぶんサクサク進めていいけど。