()は(.)の代わりに使える

例えばこういう関数群があったとして、

toStr :: Float -> String
toStr = show

length' :: String -> Int
length' = length

isBig :: Int -> Bool
isBig = (> 5)

Haskellでは($)演算子を使ってデータに対して次々に関数を適用できる。

isBig $ length' $ toStr $ 32.1

($)演算子は右結合なので、以下のように結合している。

isBig $ (length' $ (toStr $ 32.1))

逆に、左結合を強制すると型が合わずにエラーになる。

((isBig $ length') $ toStr) $ 32.1  -- エラー

さて、関数に流し込みたいデータがFunctorの中に入っている場合、Control.Applicativeモジュールの(<$>)演算子が便利である。

isBig <$> length' <$> toStr <$> Just 32.1

しかし(<$>)演算子は左結合である。つまり以下のように結合していることになる。

((isBig <$> length') <$> toStr) <$> Just 32.1

しかも(<$>)演算子の場合、右結合を強制してもちゃんと動作する。

isBig <$> (length' <$> (toStr <$> Just 32.1))

なぜ($)と違って左結合で動作するのだろうか。

ここで、(isBig <$> length')の型を調べてみる。

*Main> :i isBig <$> length'
isBig :: Int -> Bool 	-- Defined at ops.hs:11:1
(<$>) :: Functor f => (a -> b) -> f a -> f b
  	-- Defined in `Data.Functor'
infixl 4 <$>
length' :: String -> Int 	-- Defined at ops.hs:8:1
*Main> :t isBig <$> length'
isBig <$> length' :: String -> Bool

(<$>)の第2引数はlength'という関数である。そして関数はFunctorである。つまりこの場合、(<$>)は

(<$>) :: (Int -> Bool) -> (->) String Int -> (->) String Bool

このような型に束縛されていて、結果的に(String -> Bool)という型を返したのだ。

ちなみに上記の型を一般化すると以下のようになる。

(<$>) :: (b -> c) -> (->) a b -> (->) a c

これは関数合成演算子(.)と同じである。

*Main> :i (.)
(.) :: (b -> c) -> (a -> b) -> a -> c 	-- Defined in `GHC.Base'
infixr 9 .