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

Haskellでの関数定義

こんばんは、south37です。今日はHaskellで関数を定義する時に使える便利な構文について紹介して行きたいと思います。ちょっと時間がないので巻き気味になっちゃいますが、許してください!!!

パターンマッチ

Haskellには様々な便利な機能がある訳ですが、その中でもパターンマッチは群を抜いて強力です。シンプルで分かり易い構文でありながら、様々なパターンに対応する事が出来ます。

まずはシンプルな例を見てみましょう。

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

これは、factorialという関数を定義しています。1行目では、factorialの型を定義していて、::の右隣にInt -> Intと書く事でIntを受け取ってIntを返すといった定義が書かれています。

2,3行目がパターンマッチを使ったコードです。値のマッチが行われていて、0を受け取った場合には1を返し、その他の整数nを受け取った場合にはn * factorial(n - 1)を計算して返します。

これで、階乗を計算するfactorialが定義出来ました!!

データ構造に対するパターンマッチ

パターンマッチは、いろいろなデータ構造に対しても行えます。例えば、Haskellでは()で複数の値を囲む事でタプルと呼ばれるデータ構造を作る事が出来るのですが、これもパターンマッチする事が出来ます。

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

またもや、一行目はaddVectors関数の型の定義です。(Double, Double)というDouble2つからなるタプルを2つ受け取って、(Double, Double)という型の値を返しています。

二行目がパターンマッチを使った関数の定義で、タプルを二つ受け取った時にそれぞれの中身の要素にx1x2等の名前をつける事で、簡単に処理が記述出来ています。 
あまりに自然なので意識しないですが、例えばタプルの中身に引数の時点で名前をつけられないとなると、

addVectors a b = (fst a + fst b, snd a + snd b)

といった定義をする事になります。fstは(要素が2つの)タプルの左側の要素を、sndは右側の要素を返す関数です。見比べてみれば、パターンマッチの強力さを実感すると思います。

関数の型の定義について

余談になりますが、ここでaddVectorsの型の定義について考えてみたいと思います。addVecotrsが2つの引数をとって1つの値を返す関数なら、直感的には

haskell :: ( (Double, Double), (Double, Double) ) -> (Double, Double)

みたいに書けても良さそうですが、実はこういった書き方は出来ません。何故なら、Haskellにおいては実は複数引数の関数というものは定義出来ないからです。

矛盾した事を言っているようですが、そうでもなくて、Haskellにおいてはカリー化を利用する事によって複数引数を取る関数(っぽく見えるもの)を定義出来るようにしています。つまり、多変数引数の一階関数を、1引数の高階関数として定義する訳です。

一見面倒な事をやってるようですが、このおかげで任意の数の引数を受け取った状態で新たに名前をつけたりして、利便性が高まっています。

リストのパターンマッチ

パターンマッチに戻りましょう。次は、リストというデータ構造のパターンマッチです。Haskellにおけるリストは、[]で同じ型の値を囲む事によって作る事が出来ます。特定の範囲のリストや無限リストを簡単に作る為の構文も用意されています。

リストは、:という中置関数を使う事によって、先頭に要素を一つ追加する事が出来ます。1 : [2, 3][1, 2, 3]は同じ値を表す訳です。

逆に言えば、(x:xs)といったパターンでパターンマッチを行う事で、引数として受け取ったリストの先頭の要素にxという名前をつける事が出来ます。

myReverse :: [a] -> [a]
myReverse [] = []
myReverse (x:xs) = myReverse xs ++ [x]

myReverseはリストを受け取って、それを逆順にしたリストを返す関数です。任意の型をaで表して(aIntegerDoubleに相当)、任意の型のリストを[a]と表す事で、任意の型を受け取って任意の型を返すという定義がなされています。

2行目では、引数が要素の無い空のリストであれば空のリストを返すという処理を行っています。空のリストのreverseは空のリスト、まあそうですよね。

3行目では、リストを受け取った時に、最初の要素xとそれ以降の要素のリストxsにリストを分けて、xsを逆順にしたもの(myReverse xs)に要素がxだけのリスト([x])を後ろからくっつけています。(++は、リスト同士を連結する為の中置関数です)。

この定義なら、間違い無く逆順が作れますね!!

関数の定義の際は、大体これらの例のようにパターンマッチで一番単純なケースを最初に抑えて、それからもっと一般的な場合の処理を書く事になります。パターンマッチは上から順に行われる為、特別なケースは上に書いておく必要があります。

ガード

ここまでガンガンパターンマッチで関数を定義して来た訳ですが、時には引数の構造では無く、引数の満たす性質で場合分けしたいものです。そういったケースに対応する為に、ガードと呼ばれる機能も用意されています。

bmiTell :: Double -> String
bmiTell bmi
  | bmi <= 18.5 = "Tou're underweight"
  | bmi <= 25.0 = "You're supposedly normal."
  | bmi <= 30.0 = "You're fat!"
  | otherwise   = "You're a whale"

ガードは、|を使って定義します。上の例では、Doubleを受け取ってStringを返すbmiTell関数を定義している訳ですが、bmiの値に応じて関数が返す文字列を変更しています。もはや説明しなくても大体伝わってると思うのですが、| (Bool式) = Bool式が満たされた場合に関数が返す値みたいな構文になっています。

ガードもやっぱり上から順番に評価されていくので、一番特殊なケースを最初に抑える事になります。

その他の機能

他にも、where句let式case式などが関数定義の際に使われる構文です。興味が出た方は調べてみて下さい。僕はもう力尽きました...

ちなみに、実はパターンマッチはcase式のただの糖衣構文で、ただcase ofを読み易く書けるようにしただけです。その辺も、一度おさえておくといいかもですね。

まとめ

パターンマッチは強力。便利。

次回らへんでHaskellの型について書きます!多分!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!