Edited at

すごいH本読んだからまとめ 4 「関数の構文」


はじめに


関数の構文


  • Haskellの関数を定義するときに使える構文達の紹介


パターンマッチ


  • 引数による関数の場合分け

  • 引数の値をて関数が使うための値に分解できる。

lucky :: (Integral a) => a -> String  -- 「整数値として扱える型(IntとかIntegerとか)」を受け取って文字列を返す

lucky 7 = "LUCKY NUMBER SEVEN!" -- 整数値が7のときの処理
lucky x = "Sorry, you're out of luck, pal!" -- それ以外の変数のとき


  • 一番上の行で型定義をしたあとに実際の関数の動作を受け取った値のパターンごとに定義している

  • 左辺は引数の数だけ関数名 引数パターン1 引数パターン2 ...と定義できる

  • パターンマッチは上から順に行っていき、引っかかるパターンがあったらその関数を実施する(xを先頭に持ってくるとlucky 7は呼ばれない)

*Main> lucky 4

"Sorry, you're out of luck, pal!"
*Main> lucky 7
"LUCKY NUMBER SEVEN!"

パターンマッチに引っかからない関数を場合がある関数と書くとエラーが発生する

-- 7の場合だけ定義

lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"

-- 7以外の値を引数にして関数実施
*Main> lucky 4
"*** Exception: myfunction.hs:11:1-31: Non-exhaustive patterns in function lucky


タプルパターンマッチ


  • タプルの形をそのままパターンマッチに使うことができる。

-- 0以外の数のタプルの中身を交換する関数

tupleExchange :: (Int, Int) -> (Int, Int)
tupleExchange (0, x) = (0, x)
tupleExchange (x, 0) = (x, 0)
tupleExchange (a, b) = (b, a)


  • 結果

*Main> tupleExchange (7, 8)

(8,7)
*Main> tupleExchange (0, 8)
(0,8)
*Main> tupleExchange (7, 0)
(7,0)


リストパターンマッチ

リストのパターンマッチについて確認する前に少し補足


  • Haskellのリスト[要素1,要素2,...]要素1:要素2:...:[]のシンタックスシュガーになっている。

-- 左辺と右辺は同じもの

-- 僕たちがリストとして認識しやすいので左辺の表記が用意されている。
*Main>[1,2,3,4,5] == 1:2:3:4:5:[]
True

上の補足を踏まえるとこういうパターンマッチが可能

-- リストの先頭を取得する関数(head関数と同じ)

listFirst :: [a] -> a
listFirst (x:xs) = x

*Main> listFirst [1,2,3,4,5]
1

-- リストの2番目を取得する関数
listSecond :: [a] -> a
listSecond (_:x:xs) = x -- 「アンダーバー(_)」は「使いませんよ」の意味

*Main> listSecond [1,2,3,4,5]
2


asパターン

リストとかタプルで分解して定義した引数をまとめて1つの値として使いたいときに使う。



  • name@(分解した値全体)とすることでnameを分解した値全体として扱うことができる。

capital :: String -> String  

capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

*Main> capital "Dracula"
"The first letter of Dracula is D"


ガード


  • 引数が満たす条件によって関数の結果を場合分けする


-- | の記号を使って条件訳を行う

greaterNumber :: Int -> Int -> Int
greaterNumber x y
| x > y = x -- 引数の条件で返り値を変更する
| otherwise = y -- otherwiseは上の条件に当てはまらないとき

*Main> greaterNumber 9 8
9
*Main> greaterNumber 7 8
8
*Main> greaterNumber 8 8
8


  • パターンマッチは引数が持つ状態そのものにマッチするか(Int型の数値や、空のリスト等...)をチェックしてその条件を変数として使用するためのもの

  • ガードは引数を組み合わせて自分で条件を作り出し(x > yなどの状態を定義できる)、その結果をもとに返り値を決めるためのもの。


where


  • 定義した関数内に引数以外の新たな値を定義(変数への拘束)したいときに使う


-- bmiごとにコメントを返す関数

bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!" -- 引数にbmiというものはない
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2 -- ここでbmiを定義してあげる。

-- 上の関数の基準になっている体重もwhereで定義できる。(18.5, 25.0, 30.0)
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5 -- 複数の値をwhereに定義できる。
normal = 25.0
fat = 30.0

whereのパターンマッチ


-- さっきのbmiを返す関数のwhere句をタプルパターンマッチに変更できる。
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)

whereでの関数定義

calcBmis :: (RealFloat a) => [(a, a)] -> [a]  

calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
-- bmi関数を定義して、体重と身長を与えたらbmiの計算結果を返すように定義
-- リスト内包表記の出力部分で受け取った関数を実施する。

-- 出力結果
*Main> calcBmis [(65, 1.82), (70, 1.82), (75, 1.82)]
[19.623233908948194,21.132713440405748,22.6421929718633]


let


  • 定義した関数内に引数以外の新たな値を定義(変数への拘束)したいときに使う


  • let <変数への拘束> in <出力>という形で使用できる。

  • 式としての値を常にもつ

  • 値としてどこでも使用できる。

-- 円柱の表面積を求める関数(関数定義の中で使用)

cylinder :: (RealFloat a) => a -> a -> a
cylinder r h = -- 円の半径 円中の高さ
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea

-- 式の中でletを定義してみる
*Main Lib> 4 * (let max' a b = max a b in max' 2 3) + 2
14

-- タプルの定義の中でletを定義してみる
*Main Lib> let multiple a b = a * b in ((multiple 3 2 ), (multiple 2 1))
(6,2)

-- リスト内包表記の中に定義
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]

*Main Lib> calcBmis [(90, 1.8), (85, 1.8), (80, 1.8), (75, 1.8), (70, 1.8), (65, 1.8)]
[27.777777777777775,26.234567901234566]


case


  • 関数定義の引数以外でもパターンマッチの構文が使える

  • むしろパターンマッチがケース式のシンタックスシュガー

  • case文も式として使用するのでcaseの結果そのものが値になる

構文

case  of  パターン -> 結果  

パターン -> 結果
パターン -> 結果
...


-- zeroかどうかチェックする。
-- パターンマッチでも書ける。
checkZero :: Int -> Bool
checkZero x = case x of 0 -> True
x -> False

*Main> checkZero 3
False
*Main> checkZero 0
True