Haskell 勉強メモ4
関数を書くための構文
パターンマッチ
Scala でもたくさん使うパターンマッチ。Haskell版も見てく。
渡された数が7かどうかを判別する関数。
lucky :: Int -> String lucky 7 = "LUCKY NUMBER SEVEN!" lucky x = "Sorry, you're out of luck, pal!" *Main> lucky 8 "Sorry, you're out of luck, pal!" *Main> lucky 7 "LUCKY NUMBER SEVEN!"
lucky
を呼ぶと、パターンが上から下の順で試される。
def lucky(n: Int): String = n match { case 7 => "LUCKY NUMBER SEVEN!" case _ => "Sorry, you're out of luck, pal!" } lucky(8) // val res11: String = Sorry, you're out of luck, pal! lucky(7) // val res12: String = LUCKY NUMBER SEVEN!
階乗計算するプログラムをパターンマッチを使って、再帰的に書く。
factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial (n - 1)
Scalaの場合
def factorial(n: Int): Int = n match { case 0 => 1 case k => k * factorial(k-1) }
タプルのパターンマッチもできる。
2次元ベクトルの足し算。
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors a b = (fst a + fst b, snd a + snd b) -- これだと読みづらい。 addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) -- こう書くと直感的
リスト内包表記でもパターンマッチが使える。
*Main> let 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] -- マッチしない要素は結果に反映されない。 *Main> [x*100 + 3 | (x, 3) <- xs] [103,403,503]
val list = Seq((1,3),(4,3),(2,4),(5,3),(5,6),(3,1)) for ((a, b) <- list) yield a + b // val res13: Seq[Int] = List(4, 7, 6, 8, 11, 4) for ((x, 3) <- list) yield x*100 + 3 // val res14: Seq[Int] = List(103, 403, 503)
リストもパターンマッチで使える。空のリスト []
または、:
を含むパターンと合致させることができる。x:xs
というパターンは、リストの先頭要素を x
に束縛し、リストの残りを xs
に束縛する。
head' :: [a] -> a head' [] = error "Can't call head on an empty list, dummy!" head' (x: _) = x *Main> head' [] *** Exception: Can't call head on an empty list, dummy! CallStack (from HasCallStack): error, called at baby.hs:40:12 in main:Main *Main> head' [2,3,4] 2
Scalaのパターンマッチの場合は下記。
def head[T](list: List[T]): T = list match { case x::_ => x case Nil => throw new IllegalArgumentException } head(List(2,3,4)) // val res0: Int = 2 head(List()) // java.lang.IllegalArgumentException
asパターン
Haskellには as パターン という特殊なパターンがある。値をパターンに分解しつつ、パターンマッチの対象になった値自体も参照したいときに使う。@
を使うことで、元のリスト全体にアクセスすることができる。
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"
知らなかったが Scala でも同じことができるらしい。参考
上と似たことをScalaでやると。
def firstLetter(letters: List[Char]): String = { letters match { case Nil => "Empty string whoops!" case all @ (x::xs) => s"The first letter of ${all} is $x" } } firstLetter("Dracula".toList) // val res3: String = The first letter of List(D, r, a, c, u, l, a) is D
@
を使うことで、元の要素にアクセスできていることが分かる。Scalaの場合は、元の要素をそのまま使えばいいんじゃとか思うけど、何か違うのだろうか。。?
ガード
引数の値が満たす性質で場合分けするとき。
bmiTell :: Double -> String bmiTell bmi | bmi <= 18.5 = "underweight." | bmi <= 25.0 = "normal" | bmi <= 30.0 = "fat" | otherwise = "Are you Whale?"
参考本のサンプルはもっと毒々しかったけど、書くの面倒だった。bmi
の値で場合分けしている。
Scalaで書く場合は
def bmiTell(bmi: Double): String = bmi match { case v if v <= 18.5 => "underweight" case v if v <= 25.0 => "normal" case v if v <= 30.0 => "fat" case v => "Are you whale?" }
複数引数でも可能。
bmiTell :: Double -> Double -> String bmiTell weight height | weight / height ^ 2 <= 18.5 = "underweight." | weight / height ^ 2 <= 25.0 = "normal" | weight / height ^ 2 <= 30.0 = "fat" | otherwise = "Are you Whale?"
where
命令型のプログラミング言語なら、同じ値を何度を計算するのを避けるため、変数に格納するやることが一般的。Haskellでは、where
キーワードをつかて計算の中間結果に名前をつける。さっきの bmiTell
だと where /height ^ 2
を何度も書く&再評価するのは無駄が多い。下記のように修正する。
bmiTell :: Double -> Double -> String bmiTell weight height | bmi <= 18.5 = "underweight." | bmi <= 25.0 = "normal" | bmi <= 30.0 = "fat" | otherwise = "Are you Whale?" where bmi = weight / height ^ 2
where
のなかでパターンマッチを使うこともできる。
initials :: String -> String -> String initials firstname lastname = [f] ++ " " ++ [l] ++ "." where (f : _) = firstname (l : _) = lastname
where
ブロック内では、定数だけでなく関数も定義できる。
calcBmis :: [(Double, Double)] -> [Double] calcBmis xs = [bmi w h | (w, h) <- xs] where bmi weight height = weight / height ^ 2