Skip to content

Instantly share code, notes, and snippets.

@oyakata
Created September 8, 2011 16:16
Show Gist options
  • Save oyakata/1203794 to your computer and use it in GitHub Desktop.
Save oyakata/1203794 to your computer and use it in GitHub Desktop.
Haskell: 関数の合成

関数の合成

合成演算子は . (ドット):

f . g = ¥x -> f (g x)

つまり、右辺に引数を渡した結果を左辺に渡します。 ここから、以下のルールが導き出されます。

関数を合成するときのルール

  • 右辺の戻り値の型が左辺の引数と同じであること。

式で表すと、

(.) :: (b -> c) -> (a -> b) -> (a -> c)

引数に渡した関数を2度実行するtwiceという関数を定義する。

関数を2回実行するtwice関数:

twice :: (a -> a) -> (a -> a)
twice f = f . f

このようになります。

畳込関数 foldr

次に、twiceとは別に、引数に関数のリストを受けて関数を返すcomposeという関数を定義します。

関数のリストを受け取って一つの関数に集約する:

compose :: [([a] -> [a])] -> ([a] -> [a])
compose = foldr (.) id

ここで、foldrという関数は

  1. 第1引数に関数を取る。
  2. 第2引数に初期値を取る。
  3. 第3引数にリストを取る。

という高階関数です。 わかり易い例では、0を初期値に整数のリストの合計値を算出する関数が挙げられます。 仮にこの関数をsumxと名付けると、以下の定義になります。

foldr の利用例::
sumx = foldr (+) 0 print $ sumx [1..10] -- => 55

Haskellでは()で演算子を囲むと関数として使えます。

このfoldrを使って、「関数のリストをすべて合成した関数」を作ることも可能です。 それが上記composedになります。 foldrを使って関数のリストを合成して関数を作る為には、「初期値としての関数」が必要になります。 こういう場合に役に立つのが恒等関数idです。

恒等関数 id

恒等関数idは、引数をそのまま返します。

例えば、

>>> f = even . id
>>> f 1 -- => False

このように、idはいかなる関数と合成しても合成相手をまったく変更しません。 つまり、foldrから関数のリストを合成するときの初期値にうってつけの関数なのです。

実際にfoldrとidを使って関数合成を行う

更に、composeを使って

  1. 引数のリストの各要素を2倍したリストを返す関数
  2. 引数のリストから偶数だけを取り出したリストを返す関数

を合成したcomposedという関数を作ります。

>>> composed :: Integral a => [a] -> [a]
>>> composed = compose [map (*2), filter even]

最後にcomposedをtwiceに渡して2度実行させる関数を作り、[1..10]を引数に渡して呼んでみます。

>>> twice composed [1..10]

-- => [8,16,24,32,40]

生成した関数から実際に結果を得ることができました。

module Compose where
-- 合成演算子 .
-- f . g = \x -> f (g x)
twice f = f . f
compose = foldr (.) id [map (*2), filter even]
composed = twice $ compose
composed2 = twice compose
-- これだけはコンパイルが通らない。
-- 理由は、composeにリストを渡した時点でcallableでなく値になってしまうから。
-- twiceは引数にcallableを取る。
-- 対して、composeは引数にリストを取る。
-- つまり、composeはtwiceの引数たりうるが、twiceと合成できない。
-- composed3 = twice . compose
-- だから、callableであるtwiceを引数に取ったtwiceにcomposeを渡すのはok。
composed4 = twice twice compose
-- ここでわかったことは、合成するときは引数と戻り値の型が合わないとダメということ。
main :: IO()
main = print $ (sum $ composed2 [1..10]) + (sum $ composed4 [1..10])
-- => 600 (120 + 480 = 600)
module Compose2 where
twice :: (a -> a) -> (a -> a)
twice f = f . f
compose :: [([a] -> [a])] -> ([a] -> [a])
compose = foldr (.) id
-- Integral : 整数型
composed :: Integral a => [a] -> [a]
composed = compose [map (*2), filter even]
main :: IO()
main = print $ twice composed [1..10]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment