関数

このページでは,関数について説明します.

関数の定義

一般に,関数定義は次のように書きます.

関数名 引数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

$ 演算子は,最も優先順位の低い右結合の中置演算子として,次のように定義されています.

f $ x = f x

関数の型

一般に,関数の型は次のように書きます.

引数型 -> 返却型

複数の引数をもつ関数の場合は,次のように書きます.

引数型1 ->  ... -> 引数型n -> 返却型

先の add 関数の型を明示するには,次のように書きます. (ついでに,main の型も書きました.)

add :: Int -> Int -> Int
add x y = x + y

main :: IO ()
main = print $ add 2 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

相互再帰関数もたまに使います. 次のプログラムで定義している関数 evenodd は,それぞれ,引数が偶数,奇数のときに 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

パターンマッチング (関数定義)

次のプログラムは,階乗を計算する関数 factorial を,パターンマッチングを使って定義したものです.

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

main = print $ factorial 5  -- 出力: 120

引数を複数もつ関数の場合も,同様にパターンマッチを利用した定義ができます. 次のプログラムは,累乗関数 power を定義したものです (power e b = be).

power :: Int -> Double -> Double
power 0 _ = 1.0
power x y = y * power (x - 1) y

main = print $ power 8 2.0  -- 出力: 256

case 式の場合と同じく,関数定義でパターンマッチングを行う場合も,パターンの順番には意味があります. パターンは上のほうからマッチするか調べられ,最初にマッチしたパターンに対する定義が採用されます.

ガード (関数定義)

関数定義でも,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

高階関数

関数を引数や返り値に持つ関数のことを,高階関数 (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 は,この後に出てくる「関数の部分適用」の説明を読めば理解できます.

関数式

関数式 (function expression) とは,関数を表す式のことです. ラムダ抽象 (lambda abstraction),匿名関数 (anonymous function),無名関数 (unnamed function) などともいいます.

関数式を使うと,2 数の和を求める関数 add の定義は次のように記述できます.

add = \x y -> x + y

一般に,関数式は次のように書きます.

\ 引数1  ... 引数n -> 定義

最初のバックスラッシュ \ は,ラムダ計算の λ に対応しています.

次の式は,map square [1, 2, 3, 4, 5] と等価な式を,関数式を用いて書いたものです.

map (\x -> x * x) [1, 2, 3, 4, 5]  -- 値: [1, 4, 9, 16, 25]

関数の部分適用

次のプログラムは,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 -> IntInt -> (Int -> Int) と同じです. つまり,-> は右結合です.

関数 mult のように部分適用の可能な関数は,カリー化 された (curried) 関数といいます. 逆に,非カリー化された (uncurried) 関数というのは,次に示す関数 mult' のようなものを指します.

mult' :: (Int, Int) -> Int
mult' (x, y) = x * y

ちなみに,カリー化 (currying) という用語は,Haskell 言語の名前の由来でもある数学者 Haskell Curry の名からきています. 食べ物のカレーとは関係ありませんよ!

関数合成

関数 g :: a -> b と関数 f :: b -> c の合成を f . g と書きます(数学ではこれを f ∘ 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

関数合成演算子 . は,右結合の演算子として次のように定義されています.

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)

ポイントフリースタイル

先の例の foo = f . g . h のように,仮引数が現れない関数定義のスタイルを,ポイントフリースタイル (pointfree style) といいます.

ここからはまったくもってお遊びですが,次の簡単な関数 foo をポイントフリースタイルで書く練習をしてみましょう.

foo x y = f x (g y)

なお,この例では,「演算子」のページで説明する「セクション」という記法を使います. 記号 $\oplus$ を中置演算子とするとき,関数 \x -> x $\oplus$ a のことを ($\oplus$ a) と書く記法です.

ポイントフリースタイルへの変換は,関数合成を巧みに使って順番に変数を追いやることで行います. 今回の 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

というわけで,foo はポイントフリースタイルで次のように書けることがわかりました.

foo = (. g) . f