関数
このページでは,関数について説明します.
1. 関数の定義
一般に,関数定義は次のように書きます.
関数名 引数1 ... 引数n = 式
関数適用も関数定義と同様,引数を空白で区切って並べて,次のように書きます.
関数名 引数1 ... 引数n
次のプログラムは,2 数の和を計算する関数 add
を定義した例です.
add x y = x + y
main = print (add 2 3) -- 出力: 5
多くの手続き型言語とは異なり,Haskell は式ベースの言語です. 関数定義の右辺には,文ではなく式を書くことに注意します.
関数適用はどの中置演算子より高い優先順位を持ちます.
そのため,次の式は add 1 (2 * 3)
ではなく (add 1 2) * 3
と評価されます.
add 1 2 * 3 -- 値: 9
関数適用のときに便利な $
演算子を紹介しましょう.
この演算子は関数適用の区切りに用いられ,カッコの多くなった式を読みやすくします.
次の 2 つの式はそれぞれ同じ意味となります.
square (double (2 * 4)) -- 値: 256
square $ double $ 2 * 4 -- 値: 256
|
2. 関数の型
一般に,関数の型は次のように書きます.
引数型 -> 返却型
複数の引数をもつ関数の場合は,次のように書きます.
引数型1 -> ... -> 引数型n -> 返却型
先の add
関数の型を明示するには,次のように書きます.
(ついでに,main
の型も書きました.)
add :: Int -> Int -> Int
add x y = x + y
main :: IO ()
main = print $ add 2 3
3. 再帰関数
Haskell をはじめとする関数型言語では,ループ構文が存在しないため,再帰関数が多用されます.
次のプログラムは,階乗を計算する関数 factorial
を定義したものです.
factorial :: Int -> Int
factorial x =
if x == 0 then 1
else x * factorial (x - 1)
main = print $ factorial 5 -- 出力: 120
例えば,式 factorial 3
は,次のように展開されて評価されます.
factorial 3
= 3 * factorial 2
= 3 * (2 * factorial 1)
= 3 * (2 * (1 * factorial 0))
= 3 * (2 * (1 * 1))
= 6
相互再帰関数もときどき使われます.
次のプログラムで定義している関数 even
, odd
は,それぞれ,引数が偶数,奇数のときに True
を返す関数です.
import Prelude hiding (even, odd) -- 標準関数の even, odd を隠すおまじない
even :: Int -> Bool
even x = x == 0 || odd (x - 1)
odd :: Int -> Bool
odd x = x /= 0 && even (x - 1)
main :: IO ()
main = do
print $ even 0 -- 出力: True
print $ even 1 -- 出力: False
print $ even 2 -- 出力: True
print $ even 3 -- 出力: False
print $ odd 0 -- 出力: False
print $ odd 1 -- 出力: True
print $ odd 2 -- 出力: False
print $ odd 3 -- 出力: True
4. パターンマッチング(関数定義)
次のプログラムは,階乗を計算する関数 factorial
を,パターンマッチングを使って定義したものです.
factorial :: Int -> Int
factorial 0 = 1
factorial x = x * factorial (x - 1)
main = print $ factorial 5 -- 出力: 120
引数を複数もつ関数の場合も,同様にパターンマッチを利用した定義ができます.
次のプログラムは,累乗関数 power
を定義したものです($\mathsf{power}\ x\ y = y^x$).
power :: Int -> Double -> Double
power 0 _ = 1.0
power x y = y * power (x - 1) y
main = print $ power 8 2.0 -- 出力: 256
case
式の場合と同じく,関数定義でパターンマッチングを行う場合も,パターンの順番には意味があります.
パターンは上のほうからマッチするか調べられ,最初にマッチしたパターンに対する定義が採用されます.
5. ガード(関数定義)
関数定義でも,case
式と同じようにガードを利用することができます.
これにより,パターンマッチと論理を組み合わせた制御が簡単にできます.
次のプログラムは,負の指数にも対応した累乗関数 power
を定義したものです.
power :: Int -> Double -> Double
power 0 _ = 1.0
power x y | x > 0 = y * power (x - 1) y
| otherwise = 1.0 / power (- x) y
main = print $ power (-3) 2.0 -- 出力: 0.125
6. 高階関数
関数を引数や返り値に持つ関数のことを,高階関数(higher-order function)といいます.
次の例に示す map
関数は,高階関数の代表的な例です.
map
関数は関数とリストを引数に持ち,リストの各要素に関数を適用した結果を返します
(つまり,map f [x1, …, xn] = [f x1, …, f xn]
).
square :: Int -> Int
square x = x * x
main :: IO ()
main = do print $ map id [1, 2, 3, 4, 5] -- 出力: [1, 2, 3, 4, 5]
print $ map succ [1, 2, 3, 4, 5] -- 出力: [2, 3, 4, 5, 6]
print $ map square [1, 2, 3, 4, 5] -- 出力: [1, 4, 9, 16, 25]
ここで,恒等関数 id
は標準で定義されている関数で,引数をそのまま返します.
また,後者関数 succ
も標準で定義されている関数で,整数の場合は + 1 した値を返します.
次のプログラムは,引数の関数 f
を 2 回適用する関数 twice
を定義したものです.
twice :: (a -> a) -> a -> a
twice f x = f (f x)
square :: Int -> Int
square x = x * x
main = do print $ twice id 2 -- 値: 2
print $ twice succ 2 -- 値: 4
print $ twice square 2 -- 値: 16
print $ twice twice square 2 -- 値: 65536
最後の行の twice twice square 2
は,この後に紹介する "関数の部分適用" を利用しています.
7. 関数式
関数式(function expression)とは,関数を表す式のことです. "ラムダ抽象"(lambda abstraction),"匿名関数"(anonymous function),"無名関数"(unnamed function)などともいいます.
一般に,関数式は次のように書きます.
\ 引数1 ... 引数n -> 定義
最初のバックスラッシュ \ は,ラムダ計算の λ に対応しています.
|
関数式を使うと,2 数の和を求める関数 add
の定義は次のように記述できます.
add = \x y -> x + y
次の式は,map square [1, 2, 3, 4, 5]
と等価な式を,関数式を用いて書いたものです.
map (\x -> x * x) [1, 2, 3, 4, 5] -- 値: [1, 4, 9, 16, 25]
8. 関数の部分適用
次のプログラムは,2 数の積を求める関数 mult
と,2 倍の値を求める関数 double
を定義したものです.
mult :: Int -> Int -> Int
mult x y = x * y
double :: Int -> Int
double = mult 2
main = print (double 3) -- 出力: 6
関数 double
は,mult
を部分的に適用して定義されています.
これを関数の 部分適用(partial application)といいます.
関数適用は左結合であるため,例えば式 mult 2 3
は (mult 2) 3
と評価されます.
また,関数適用の左結合性より,関数 mult
の型 Int -> Int -> Int
は Int -> (Int -> Int)
と同じです(つまり,->
は右結合です).
関数 mult
のように部分適用の可能な関数のことを,カリー化 された(curried)関数といいます.
逆に,非カリー化された(uncurried)関数というのは,次に示す関数 mult'
のようなものを指します.
mult' :: (Int, Int) -> Int
mult' (x, y) = x * y
ちなみに,カリー化(currying)という用語は,Haskell 言語の名前の由来でもある数学者 Haskell Curry の名からきています. 食べ物のカレーとは関係ありません! |
9. 関数合成
関数 g :: a -> b
と関数 f :: b -> c
の合成を f . g
と書きます(数学ではこれを $f \circ g$ と書きます).
式 (f . g) x
は,式 f (g x)
と同じ意味です.
次に示すのは,foo x = f (g (h x))
という関数を 3 通りの定義で書いたものです.
どの定義も同じ意味になります.
foo x = f (g (h x))
foo x = (f . g . h) x
foo = f . g . h
関数合成演算子
|
10. ポイントフリースタイル
先の例の foo = f . g . h
のように,仮引数が現れない関数定義のスタイルを,ポイントフリースタイル (pointfree style) といいます.
ここで,次の簡単な関数 foo
をポイントフリースタイルで定義する練習をしてみましょう!
foo x y = f x (g y)
ポイントフリースタイルへの変換は,関数合成を巧みに使って順番に変数を追いやることで実現できます.
今回の foo
の例の場合は,次のようにして変数を消していきます.
foo = \x y -> f x (g y)
= \x y -> (f x . g) y
= \x -> f x . g
= \x -> (. g) (f x)
= \x -> ((. g) . f) x
= (. g) . f
この例では,"演算子" のページで説明する "セクション" という記法を使っています. 記号 $\oplus$ を中置演算子とするとき,関数 \x -> x $\oplus$ a のことを ($\oplus$ a) と書く記法です. |
というわけで,foo
はポイントフリースタイルで次のように書けることがわかりました.
foo = (. g) . f