すごいHaskell 第3章 関数の構文 メモ
この章では関数のパターンマッチ、ガード、where、let式、case式 について書いてある。
パターンマッチ
パターンマッチはパターンに従って、データを分解するために用いる。 関数の本体を下の例のように分解することもできる。 パターンは上から順に評価される。パターンに小文字から始まる名前を書くと、任意の値にマッチするようになる。
luckySeven :: Int -> String luckySeven 7 = "LUCKY NUMBER SEVEN!" luckySeven x = "Sorry, you're out of luck, pal!"
これは if/else で下のように書くのと同じ。
luckySeven :: Int -> String luckySeven x = if x == 7 then "LUCKY NUMBER SEVEN!" else "Sorry, you're out of luck, pal!"
パターンマッチを使った再帰関数の例 (factorial)
factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial (n - 1)
次の例はNon-exhaustive patterns in function(パターンが網羅的でない)とエラーがでる。
charName :: Char -> String charName 'a' = "Albert" charName 'b' = "Broseph" charName 'c' = "Cecil"
a, b, c以外のパターンにマッチしない場合の処理が抜けているので、エラーになる。関数型言語の関数は何らかの値を返さないといけないので、エラーとなる。if/else で考えると else の処理がないのと同じ意味。上の例のエラーをなくすには
charName _ = "Other"
のように、'a', 'b', 'c' 以外のパターンの処理を書く必要がある。
※Haskellは遅延評価を行うので、ファイルを読み込んだ時点ではエラーは表示されない。つまり、
charName 'h'
と評価するまで、エラーにはならない。.hsファイルの先頭に下のオプションを追加することで、エラーチェックをGHCが行ってくれる。
{-# OPTIONS -Wall -Werror #-}
タプルのパターンマッチ
2次元ベクトルの和をパターンマッチを使用しないで、書くと次のように書ける。
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors a b = (fst a + fst b, snd a + snd b)
引数の a b の部分にパターンマッチを使用して書くと、
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
より自然になる。
リストのパターンマッチとリスト内包表記
リスト内包表記にもパターンマッチが使用できる。
let xs = [(1,3), (4,3), (2, 4), (5,3), (5,6), (3,1)] [a+b | (a, b) <- xs] -- 出力:[4,7,6,8,11,4]
リスト [1,2,3] は 1:2:3:[] の構文糖衣なので、リストも次のようにして、パターンマッチで使える。
let x:xs = [1..10] x -- 1 xs -- [2,3,4,5,6,7,8,9,10]
- リストに対してパターンマッチを使った関数の例
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
asパターン
asパターンを使えばパターンマッチの対象となった値自体も参照できる。 asパターンの構文はパターンの前に @ (アットマーク)をつけて、参照する変数名を書く。
variable_name@(some_pattern)
- 例
let all@(x:xs) = [1,2,3,4] all -- 出力:[1,2,3,4] x -- 出力:1 xs -- 出力:[2,3,4]
場合分けして、きっちりガード!
関数の引数によって場合分けをするときは、ガードを使う。ガードはif文と似ているが、可読性が高い。 ガードは以下のような構文になる。
function_name param | expression1 = return value if expression1 is TRUE | expression2 = return value if expression2 is TRUE | otherwise = return value if the above expressions are FALSE
- 肥満度指数(BMI)から叱り方を返す例
bmiTell :: Double -> String bmiTell bmi | bmi <= 18.5 = "Your're underweight." | bmi <= 25.0 = "You're normal." | bmi <= 30.0 = "You're fat." | otherwise = "You're whale."
- max関数の例
max' :: (Ord a) => a -> a -> a max' a b | a <= b = b | otherwise = a
- compare関数の例
compare' :: (Ord a) => a -> a -> Ordering compare' a b | a == b = EQ | a <= b = LT | otherwise = GT
Where?!
関数の最後ににwhere キーワードを使って値を変数に束縛(バインド) することができる。
let x = 1 + y + z where y = 1; z = 1 x -- 出力:3
※ where節で複数の変数を使うとき、インデントを揃えないとエラーになる
Whereのスコープ
- 関数の外からは見えない
- 関数の違うパターンの本体からは見えない
次の例はniceGreetingがスコープの範囲外というようなエラーになる
greeting :: String -> String greeting "Juan" = niceGreeting ++ " Juan!!" greeting "Fernando" = niceGreeting ++ " Fernando!!" greeting name = badGreeting ++ " " ++ name where niceGreeting = "Hello! So very nice to see you," badGreeting = "Oh! Pfft. It's you."
whereとパターンマッチ
whereの中でもパターンマッチが使用できる。次の関数initalsはfirstnameとlastname を引数にとり、whereの中でパターンマッチを行い、イニシャルを返す。
initials :: String -> String -> String initials firstname lastname = [f] ++ "." ++ [l] where (f:_) = firstname (l:_) = lastname
whereブロックの中の関数
whereブロックの中では関数も定義できる。 次の関数calcBmisは体重と身長のペアのリストを受け取ってBMIのリストを返す。
calcBmis :: [(Double, Double)] -> [Double] calcBmis xs = [bmi w h | (w, h) <- xs] where bmi weight height = weight / height ^ (2 :: Integer)
※ {-# OPTIONS -Wall -Werror #-} を指定していると型を明示的に (2 :: Int) としないと次のような警告がでる。型がわからないと言っているようだが、詳しくは後で調査することにする。
Warning: Defaulting the following constraint(s) to type ‘Integer’ (Num b0) arising from the literal ‘2’ at ch3.hs:67:49 (Integral b0) arising from a use of ‘^’ at ch3.hs:67:47 In the second argument of ‘(^)’, namely ‘2’ In the second argument of ‘(/)’, namely ‘height ^ 2’ In the expression: weight / height ^ 2
let It Be
let式は
- どこでも変数を束縛できる
- let 自身も式になる
- 局所的でガード間では共有できない
- パターンマッチが使える
let式の構文
let 変数の束縛 in 式
- let式の例
4 * (let a = 9 in a + 1) + 2 -- 出力:42 [let square x = x * x in (square 5, suqre 3, square 2)] -- 出力:[(25,9,4)] -- セミコロン(;)が使える let a = 100; b = 200; c = 300 in a * b * c -- 出力:6000000 -- パターンマッチ (let (a, b, c) = (1, 2, 3) in a + b + c) * 100 -- 出力:600
リスト内包表記でのlet
calcBmis' :: [(Double, Double)] -> [Double] calcBmis' xs = [bmi | (w, h) <- xs, let bmi = w / h ^ (2 :: Int)]
(w, h) <- xs の部分をジェネレータと呼ぶ。
case式
case式は
- 他の言語のcase文と同じような概念
- let式と同じで、case式も式
- パターンマッチが使える
- 関数の引数に対するパターンマッチはcase式の構文糖衣
case式の構文
case expression of pattern1 -> result1 pattern2 -> result2 ...
- 例
describeList :: [a] -> String describeList ls = "The list is " ++ case ls of [] -> "empty." [x] -> "a singleton list." xs -> "a longer list."
※ x, xsが未使用のため警告がでたので、オプションを次のように書き換えた
{-# OPTIONS -Wall -Werror -fno-warn-unused-binds -fno-warn-unused-matches #-}
余談
- この章で分からなかった漢字
- 顰蹙 (ひんしゅく)