関数

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

関数の定義

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

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

なお、この例では、「演算子」のページで説明する「セクション」という記法を使います。 記号 を中置演算子とするとき、関数 \x -> x ⊕ a のことを (⊕ 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