シリーズ :: [Qiita記事]
Haskell個人メモ :: 1.基本
Haskell個人メモ :: 2.型
Haskell個人メモ :: 3.関数の構文 ←いまここ
Haskell個人メモ :: 4.再帰
パターンマッチ
基本
-- | ラッキー?
lucky :: Int -> String -- Int型を引数に受け取り、String型を返す
lucky 7 = "Lucky!"
lucky x = "Unlucky..."
lucky 7 -- "Lucky!"
lucky 8 -- "Unlucky..."
パターンマッチは上から順番に評価される。
上記ではx
に実際の値が束縛されるが、未使用なので通常は_
で記述して値を捨てる。
再帰
-- | 階乗を求める
factorial :: Int -> Int
factorial 0 = 1 -- 0! は 1
factorial n = n * factorial (n - 1) -- n! は n * (n - 1)!
factorial 5 -- 120
パターンマッチで再帰呼び出しをすることも可能。
考慮されていないケース
fruits :: Char -> String
fruits 'A' = "Apple"
fruits 'B' = "Banana"
fruits 'A' -- "Apple"
fruits 'B' -- "Banana"
fruits 'C' -- "*** Exception: baby.hs:(17,1)-(18,21): Non-exhaustive patterns in function fruits
パターンマッチで考慮されていない、つまり末端を抜けてしまった場合は実行時エラーになる。
タプルのパターンマッチ
add :: (Int, Int) -> (Int, Int) -> (Int, Int)
add (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
add (1, 1) (2, 3) -- (3,4)
タプルは中身の値を直接マッチ(束縛)させることができる。
リスト内包表記でのパターンマッチ
let xs = [(1, 10), (2, 20), (3, 30)]
[ x + y | (x, y) <- xs ] -- [11,22,33]
リストのパターンマッチ
head' :: [a] -> a
head' [] = error "List is empty."
head' (x:xs) = x
tail' :: [a] -> [a]
tail' [] = error "List is empty."
tail' (x:xs) = xs
x:xs
という形で「先頭の要素」と「残りの要素」で分解できる。
空のリストは[]
でマッチできる。
-- | リストの先頭から2つの要素を取得する
head2 :: [a] -> [a]
head2 [] = error "List is empty."
head2 (x:y:ys) = [x, y]
x:y:ys
といった形にすることで、先頭以外の要素も分解して取得できる。
x
やxs
は命名規約ではないが、Haskellの慣習として(リストのパターンマッチでは特に)この名前が好んで使用される。
x
は単一、xs
は複数(s)という意味合いらしい。
asパターンマッチ
firstLetter :: String -> String
firstLetter s@(x:xs) = s ++ "'s first letter is " ++ [x]
firstLetter "Hello" -- "Hello's first letter is H"
全体@パターン
と書くことで、パターンで分解しつつ、全体(元の値)にもアクセスできる。
ガード
myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
| a == b = EQ
| a <= b = LT
| otherwise = GT
値の「構造」ではなく、「性質」で場合分けする場合はパターンマッチではなくガードを利用する。
if-then-else
だとネストが深くなる場合などに有効。
書式は以下のとおり。
式
| 条件1 = 条件1にマッチしたとの処理
| 条件2 = 条件2にマッチしたとの処理
| otherwise = いずれの条件にもマッチしなかった時の処理
where
変数の束縛
-- | BMI判定(体重kg、身長cm)
judgeBMI :: Double -> Double -> String
judgeBMI weight height
| bmi < 18.5 = "低体重" ++ show bmi
| bmi < 25.0 = "標準"
| bmi < 30.0 = "肥満度1"
| bmi < 35.0 = "肥満度2"
| bmi < 40.0 = "肥満度3"
| otherwise = "肥満度4"
where
bmi = weight / (heightM * heightM)
heightM = height / 100
putStrLn $ judgeBMI 67 160.0 -- 肥満度1
関数の末尾にwhere
、その後の行に変数 = 計算式
と記述することで一時変数(的な)ものを作れる。
where
内で、where
内で定義した別の変数を参照することも可能(上記ではheightM
)。
書式は以下のとおり。インデントは揃っていないとNG。
where
変数1 = 式
変数2 = 式
...
関数の束縛
hello :: String -> String
hello name = "Hello, " ++ mr name
where
mr name = "Mr." ++ name
同様に関数も束縛できる。
where内でのパターンマッチ
-- | イニシャルを取得
initials :: String -> String -> String
initials firstname lastname = [l] ++ "." ++ [f]
where
(f:_) = firstname
(l:_) = lastname
initials "Yusuke" "Hosonuma" -- "H.Y"
where内でもパターンマッチできる。
let
基本
[let square x = x * x in (square 3, square 4, square 5)] -- [(9,16,25)]
let 変数/関数 in 式
という書式で、ローカルな変数や関数(上記ではsquare
)を定義できる。
let
は式なので結果を返す(上記ではタプル)。
whereとの違い
where
と似ているが以下の点が異なる。
- どこでも書ける。
-
where
ではガードをまたぐことが出来るが、let
で定義したものはin
の中でしか参照できない。
好みの問題もあるが、基本的にはwhere
を使って、必要な場合はlet
を使うのが良さそう。
リスト内包表記内でのlet
-- | 複数のBMIを計算
bmis :: [(Double, Double)] -> [Double]
bmis xs = [ bmi | (w, h) <- xs, let hm = h / 100
bmi = w / (hm * hm) ]
bmis [(47, 160), (67, 170)] -- [18.359374999999996,23.18339100346021]
リスト内包表記内でlet
で宣言したものは、出力部(|
の前)とlet
より先のリスト内包表記内で参照できる。
case式
sayNumber :: Int -> String
sayNumber n = (show n) ++ " is "
++ case n of
1 -> "One"
2 -> "Two"
_ -> "Number!"
sayNumber 1 -- "1 is One"
sayNumber 2 -- "2 is Two"
sayNumber 3 -- "3 is Number!"
case
式はどこでも使えるパターンマッチ。
関数のパターンマッチと同等のことが式としてどこでも記述できる。
(関数のパターンマッチはcase
式のシンタックスシュガー)
書式は以下のとおり。
case 値 of
パターン1 -> 処理1
パターン2 -> 処理2
...
ガードの場合、条件式と処理を分けるのは=
であったが、こちらは->
である点に注意。