読者です 読者をやめる 読者になる 読者になる

by shigemk2

当面は技術的なことしか書かない

パターンマッチ

Haskell

パターンマッチはある種のデータが従うべきパターンを指定し、そのパターンに従って
データを分解するために使う。

Haskell では、関数を定義する歳にパターンマッチを使って関数の本体を場合分けすることが
出来る。

渡された数が7かどうかを調べる(if/else使えば簡単だけど…)
どうでもいいけど、これに文字列渡したら死にます。
なぜなら、引数はIntと指定してあるから。

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

でもこれはパターンマッチなしだと結構複雑な
if/then/elseが必要になるかと思われる。

これも同様の理由で文字列を引数にしたら死ぬ。

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"
Main> lucky 7
"LUCKY NUMBER SEVEN!"
Main> lucky 10
"Sorry, you're out of luck, pal!"
Main> lucky "Hoge"

<interactive>:5:7:
    Couldn't match expected type `Int' with actual type `[Char]'
    In the first argument of `lucky', namely `"Hoge"'
    In the expression: lucky "Hoge"
    In an equation for `it': it = lucky "Hoge"
Main> sayMe 5
"Five!"
Main> sayMe 6
"Not between 1 and 5"

関数を再帰的に定義する

factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
Main> factorial 1
1
Main> factorial 0
1
Main> factorial 5
120

一見正しいように見えるけど、
予期していない値が入ったらエラーになる。
つまり、全てに合致するパターンを最後に入れておくべきである。

charName :: Char -> String
charName 'a' = "Albert"
charName 'b' = "Broseph"
charName 'c' = "Cecil"
Main> charName 'a'
"Albert"
Main> charName 'b'
"Broseph"
Main> charName 'h'
"*** Exception: baby.hs:(33,1)-(35,22): Non-exhaustive patterns in function charName

タプルのパターンマッチも可能。

addVectors :: ((Double, Double) -> (Double, Double) -> (Double, Double))
addVectors a b = (fst a + fst b, snd a + snd b)
Main> :t addVectors
addVectors
  :: (Double, Double) -> (Double, Double) -> (Double, Double)

しかし、こちらのほうが引数がタプルであることが分かりやすいし、
タプルの要素に適切な名前がついているので読みやすくなっている。

addVectors' :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors' (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
Main> :t addVectors'
addVectors'
  :: (Double, Double) -> (Double, Double) -> (Double, Double)

独自定義。_はどうでもいい値のときに使う。

first :: (a, b, c) -> a
first (x, _, _) = x
second :: (a, b, c) -> b
second (_, y, _) = y
third :: (a, b, c) -> c
third (_, _, z) = z
Main> first (1, 2, 3)
1
Main> second (1, 2, 3)
2
Main> third (1, 2, 3)
3

なお、リストの内包表記でもパターンマッチは使えます。

# これは普通のやつだけど…
Main> let xs = [(1,3),(4,3),(2,4),(5,3),(5,6),(3,1)]
Main> xs
[(1,3),(4,3),(2,4),(5,3),(5,6),(3,1)]
Main> [a+b | (a, b) <- xs]
[4,7,6,8,11,4]
# 2番目の要素が3のとき、1番目の要素*100+3
Main> [x*100+3 | (x, 3) <- xs]
[103,403,503]


aはなんでもいい。空のときにも対応している
ここではerror関数を使ってランタイムエラーを発生させているが、
プログラムをクラッシュさせるものなのでみだりに使わないこと。

head' :: [a] -> a
head' [] = error "Can't call head on empty list, dummy!"
head' (x:_) = x
Main> head' [3, 4]
3
Main> head' [3, 4, 5, 6]
3
Main> head' []
*** Exception: Can't call head on empty list, dummy!
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "The list is long. The first two elements are: "
               ++ show x ++ " and " ++ show y
Main> tell []
"The list is empty"
Main> tell [1]
"The list has one element: 1"
Main> tell [1,2]
"The list has two elements: 1 and 2"
Main> tell [True, False]
"The list has two elements: True and False"
Main> tell [1,2,3]
"The list is long. The first two elements are: 1 and 2"

なお、パターンマッチでは++演算子は使えない。

badAdd :: (Num a) => [a] -> a
badAdd (x:y:z: [] ) = x + y + z
Main> badAdd [100,200]
*** Exception: baby.hs:65:1-31: Non-exhaustive patterns in function badAdd

Main> badAdd [100, 20, 80]
200

asパターンを利用して、値をパターンに分解しつつ、
パターンマッチの対象になった値自体も参照することが出来る。
xs@(x:y:ys)のようなasパターンを作って、x:y:ysに合致するものと全く同じものに合致しつつ、xs でもとのリスト全体に
アクセスすることも出来る。

x:y:ysとタイプしなくてもよいのだ。

firstLetter :: String -> String
firstLetter " " = "Empty string, whoops!"
firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
Main> firstLetter "Dracula"
"The first letter of Dracula is D"