LoginSignup
21
11

More than 5 years have passed since last update.

Haskell個人メモ :: 3.関数の構文

Last updated at Posted at 2016-05-08

シリーズ :: [Qiita記事]

Haskell個人メモ :: 1.基本
Haskell個人メモ :: 2.型
Haskell個人メモ :: 3.関数の構文 ←いまここ
Haskell個人メモ :: 4.再帰

パターンマッチ

基本

-- | ラッキー?
lucky :: Int -> String -- Int型を引数に受け取り、String型を返す
lucky 7 = "Lucky!"
lucky x = "Unlucky..."
ghci
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)!
ghci
factorial 5 -- 120

パターンマッチで再帰呼び出しをすることも可能。

考慮されていないケース

fruits :: Char -> String
fruits 'A' = "Apple"
fruits 'B' = "Banana"
ghci
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)
ghci
add (1, 1) (2, 3) -- (3,4)

タプルは中身の値を直接マッチ(束縛)させることができる。

リスト内包表記でのパターンマッチ

ghci
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といった形にすることで、先頭以外の要素も分解して取得できる。

xxsは命名規約ではないが、Haskellの慣習として(リストのパターンマッチでは特に)この名前が好んで使用される。
xは単一、xsは複数(s)という意味合いらしい。

asパターンマッチ

firstLetter :: String -> String
firstLetter s@(x:xs) = s ++ "'s first letter is " ++ [x]
ghci
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
ghci
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
ghci
initials "Yusuke" "Hosonuma" -- "H.Y"

where内でもパターンマッチできる。

let

基本

ghci
[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) ]
ghci
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!"
ghci
sayNumber 1 -- "1 is One"
sayNumber 2 -- "2 is Two"
sayNumber 3 -- "3 is Number!"

case式はどこでも使えるパターンマッチ。

関数のパターンマッチと同等のことが式としてどこでも記述できる。
(関数のパターンマッチはcase式のシンタックスシュガー)

書式は以下のとおり。

case  of
  パターン1 -> 処理1
  パターン2 -> 処理2
  ...  

ガードの場合、条件式と処理を分けるのは=であったが、こちらは->である点に注意。

21
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11